bug-bison
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

C++11 move semantics


From: Frank Heckenbach
Subject: C++11 move semantics
Date: Sat, 03 Mar 2018 22:36:46 +0100

Hi,

so far my C++ parsers use the C template (which works, but with
obvious restrictions). Now I'm trying to convert them to real C++
parsers using lalr1.cc.

One of the main advantages I hope to achieve is to use proper C++
classes for semantic values (where so far I've had to use manually
managed plain pointers). However, the generated parser requires
copyable types, just moveable isn't enough (even if I use std::move
in all user-defined actions).

Some of my semantic values are move-only types, some others are not
strictly non-copyable, but contain complex structures that I really
don't want to copy. unique_ptr obviously doesn't help. I could use
shared_ptr, but it doesn't have a proper[1] way to release objects
from shared_ptr, so I'd have to keep shared_ptr throughout the rest
of the code even if semantically there should be only one reference
to the object. This might cause noticeable performance problems (in
my code, the parser runs quickly, but the rest of the code is
somewhat time-critical and adding shared_ptr overhead all over the
place might be problematic).

[1] There are hacky workarounds, as discussed here:
    
https://stackoverflow.com/questions/1833356/detach-a-pointer-from-a-shared-ptr
    but I really don't want to get into that. Or one could use
    shared_ptr <unique_ptr <T>>, but that also gets ugly.

I can't imagine I'm the only one having this problem. What do other
C++11 users do in such cases?

I now see this seems to be an open problem for years. In
http://lists.gnu.org/archive/html/bug-bison/2015-01/msg00066.html
Hans Aberg made a promising approach. While it seems to remove all
copying at runtime, the code still contains calls to copy
constructors/assignment which are never reached in the test case,
but still prevent using move-only types.

So I went from there, made the changes in the templates rather than
the generated code, and hope to remove all copying of semantic types
(so I didn't even need a combined l- and r-value template with
std::forward, as was discussed back then).

Patch attached (against 3.0.4.dfsg-1+b1 from Debian stretch).

variant.hh:

- Basically the same changes Hans did, replacing const T& and copy
  by T&& and std::move where required.

- Optimized variant::move to use std::move instead of swap
  (swap would also still work, but often does too much).

- Disabled (#if 0) variant::copy to make sure it's never used.

- Explicitly disabled move constructor and assignment, otherwise the
  the templated constructor could be used instead accidentally
  (which would only fail at runtime with an assertion, as I found
  out while testing)!

- Changed static assertions from YYASSERT to static_assert
  (not strictly related, but useful if C++11 is used anyway).

c++.m4:

- basic_symbol: Constructor for symbols with semantic value:
  Use T&& and std::move, like above.

- Added move constructor/assignment for basic_symbol. Fortunately,
  I could reuse most of the code of its copy constructor, which must
  not exist anymore, to implement them.

lalr1.cc:

- Due to the previous change, stack_symbol_type::operator= becomes
  superfluous, so I took it out to make sure.

I'm still not totally happy with the code; in particular, it's now a
mixture of proper std::move usage, and existing functions taking an
lvalue reference to a steal from, but I didn't want to change much
more than necessary. And I only tested it with the calc++ example so
far (using "unique_ptr <string>"), so there may be problems hidden,
and the non-variant case is not tested at all.

I'd appreciate any feedback from the maintainers or other users who
have the same problem.

Regards,
Frank
--- orig/variant.hh     2015-08-05 09:05:30.000000000 +0200
+++ ./variant.hh        2018-03-03 21:17:13.880113143 +0100
@@ -100,11 +100,11 @@
 
     /// Construct and fill.
     template <typename T>
-    variant (const T& t)]b4_parse_assert_if([
+    variant (T&& t)]b4_parse_assert_if([
       : yytypeid_ (&typeid (T))])[
     {
-      YYASSERT (sizeof (T) <= S);
-      new (yyas_<T> ()) T (t);
+      static_assert (sizeof (T) <= S, "variant size too small");
+      new (yyas_<T> ()) T (std::move (t));
     }
 
     /// Destruction, allowed only if empty.
@@ -119,7 +119,7 @@
     build ()
     {]b4_parse_assert_if([
       YYASSERT (!yytypeid_);
-      YYASSERT (sizeof (T) <= S);
+      static_assert (sizeof (T) <= S, "variant size too small");
       yytypeid_ = & typeid (T);])[
       return *new (yyas_<T> ()) T;
     }
@@ -127,12 +127,12 @@
     /// Instantiate a \a T in here from \a t.
     template <typename T>
     T&
-    build (const T& t)
+    build (T &&t)
     {]b4_parse_assert_if([
       YYASSERT (!yytypeid_);
-      YYASSERT (sizeof (T) <= S);
+      static_assert (sizeof (T) <= S, "variant size too small");
       yytypeid_ = & typeid (T);])[
-      return *new (yyas_<T> ()) T (t);
+      return *new (yyas_<T> ()) T (std::move (t));
     }
 
     /// Accessor to a built \a T.
@@ -141,7 +141,7 @@
     as ()
     {]b4_parse_assert_if([
       YYASSERT (*yytypeid_ == typeid (T));
-      YYASSERT (sizeof (T) <= S);])[
+      static_assert (sizeof (T) <= S, "variant size too small");])[
       return *yyas_<T> ();
     }
 
@@ -151,7 +151,7 @@
     as () const
     {]b4_parse_assert_if([
       YYASSERT (*yytypeid_ == typeid (T));
-      YYASSERT (sizeof (T) <= S);])[
+      static_assert (sizeof (T) <= S, "variant size too small");])[
       return *yyas_<T> ();
     }
 
@@ -179,11 +179,14 @@
     void
     move (self_type& other)
     {
-      build<T> ();
-      swap<T> (other);
+      build<T> ();]b4_parse_assert_if([
+      YYASSERT (yytypeid_);
+      YYASSERT (*yytypeid_ == *other.yytypeid_);])[
+      as<T> () = std::move (other.as<T> ());
       other.destroy<T> ();
     }
 
+#if 0
     /// Copy the content of \a other to this.
     template <typename T>
     void
@@ -191,6 +194,7 @@
     {
       build<T> (other.as<T> ());
     }
+#endif
 
     /// Destroy the stored \a T.
     template <typename T>
@@ -202,9 +206,12 @@
     }
 
   private:
-    /// Prohibit blind copies.
+    /// Prohibit blind copies
+    /// Don't use the templated constructor (which would only fail at runtime 
with an assertion)!
     self_type& operator=(const self_type&);
+    self_type& operator=(self_type&&);
     variant (const self_type&);
+    variant (self_type&&);
 
     /// Accessor to raw memory as \a T.
     template <typename T>
@@ -290,7 +297,7 @@
     symbol_type
     make_[]b4_symbol_([$1], [id]) (dnl
 b4_join(b4_symbol_if([$1], [has_type],
-                     [const b4_symbol([$1], [type])& v]),
+                     [b4_symbol([$1], [type])&& v]),
         b4_locations_if([const location_type& l])));
 
 ])])])
@@ -314,11 +321,11 @@
 [  b4_parser_class_name::symbol_type
   b4_parser_class_name::make_[]b4_symbol_([$1], [id]) (dnl
 b4_join(b4_symbol_if([$1], [has_type],
-                     [const b4_symbol([$1], [type])& v]),
+                     [b4_symbol([$1], [type])&& v]),
         b4_locations_if([const location_type& l])))
   {
     return symbol_type (b4_join([token::b4_symbol([$1], [id])],
-                                b4_symbol_if([$1], [has_type], [v]),
+                                b4_symbol_if([$1], [has_type], [std::move 
(v)]),
                                 b4_locations_if([l])));
   }
 
@@ -332,7 +339,7 @@
 [[
   basic_symbol (]b4_join(
           [typename Base::kind_type t],
-          b4_symbol_if([$1], [has_type], const b4_symbol([$1], [type])[ v]),
+          b4_symbol_if([$1], [has_type], b4_symbol([$1], [type])&&[ v]),
           b4_locations_if([const location_type& l]))[);
 ]])
 
@@ -344,10 +351,10 @@
   template <typename Base>
   ]b4_parser_class_name[::basic_symbol<Base>::basic_symbol (]b4_join(
           [typename Base::kind_type t],
-          b4_symbol_if([$1], [has_type], const b4_symbol([$1], [type])[ v]),
+          b4_symbol_if([$1], [has_type], b4_symbol([$1], [type])&&[ v]),
           b4_locations_if([const location_type& l]))[)
     : Base (t)
-    , value (]b4_symbol_if([$1], [has_type], [v])[)]b4_locations_if([
+    , value (]b4_symbol_if([$1], [has_type], [std::move 
(v)])[)]b4_locations_if([
     , location (l)])[
   {}
 ]])
--- orig/c++.m4 2015-08-05 09:05:30.000000000 +0200
+++ ./c++.m4    2018-03-03 21:08:36.253716197 +0100
@@ -198,8 +198,9 @@
       /// Default constructor.
       basic_symbol ();
 
-      /// Copy constructor.
-      basic_symbol (const basic_symbol& other);
+      /// Move constructor and assignment.
+      basic_symbol (basic_symbol&& other);
+      basic_symbol& operator= (basic_symbol&& other);
 ]b4_variant_if([[
       /// Constructor for valueless symbols, and symbols from each type.
 ]b4_type_foreach([b4_basic_symbol_constructor_declare])], [[
@@ -209,7 +210,7 @@
 
       /// Constructor for symbols with semantic value.
       basic_symbol (typename Base::kind_type t,
-                    const semantic_type& v]b4_locations_if([,
+                    semantic_type&& v]b4_locations_if([,
                     const location_type& l])[);
 
       /// Destroy the symbol.
@@ -231,7 +232,8 @@
       location_type location;])[
 
     private:
-      /// Assignment operator.
+      /// This class is not copyable.
+      basic_symbol (const basic_symbol& other);
       basic_symbol& operator= (const basic_symbol& other);
     };
 
@@ -294,29 +296,35 @@
 
   template <typename Base>
   inline
-  ]b4_parser_class_name[::basic_symbol<Base>::basic_symbol (const 
basic_symbol& other)
-    : Base (other)
-    , value ()]b4_locations_if([
-    , location (other.location)])[
+  ]b4_parser_class_name[::basic_symbol<Base>::basic_symbol (basic_symbol&& 
other)
   {
-    ]b4_variant_if([b4_symbol_variant([other.type_get ()], [value], [copy],
-                                      [other.value])],
-                   [value = other.value;])[
+    *this = std::move (other);
   }
 
+  template <typename Base>
+  inline
+  ]b4_parser_class_name[::basic_symbol<Base>& 
]b4_parser_class_name[::basic_symbol<Base>::operator= (basic_symbol&& other)
+  {
+    static_cast<Base &> (*this) = other;]b4_locations_if([
+    location = std::move (other.location);])[
+    ]b4_variant_if([b4_symbol_variant([other.type_get ()], [value], [move],
+                                      [other.value])],
+                   [value = std::move (other.value);])[
+    return *this;
+  }
 
   template <typename Base>
   inline
   ]b4_parser_class_name[::basic_symbol<Base>::basic_symbol (]b4_join(
           [typename Base::kind_type t],
-          [const semantic_type& v],
+          [semantic_type&& v],
           b4_locations_if([const location_type& l]))[)
     : Base (t)
-    , value (]b4_variant_if([], [v])[)]b4_locations_if([
+    , value (]b4_variant_if([], [std::move (v)])[)]b4_locations_if([
     , location (l)])[
   {]b4_variant_if([[
     (void) v;
-    ]b4_symbol_variant([this->type_get ()], [value], [copy], [v])])[}
+    ]b4_symbol_variant([this->type_get ()], [value], [move], [v])])[}
 
 ]b4_variant_if([[
   // Implementation of basic_symbol constructor for each type.
--- orig/lalr1.cc       2015-08-05 09:05:30.000000000 +0200
+++ ./lalr1.cc  2018-03-03 20:54:45.326675277 +0100
@@ -316,8 +316,6 @@
       stack_symbol_type ();
       /// Steal the contents from \a sym to build this.
       stack_symbol_type (state_type s, symbol_type& sym);
-      /// Assignment, needed by push_back.
-      stack_symbol_type& operator= (const stack_symbol_type& that);
     };
 
     /// Stack type.
@@ -592,6 +590,7 @@
     that.type = empty_symbol;
   }
 
+#if 0
   inline
   ]b4_parser_class_name[::stack_symbol_type&
   ]b4_parser_class_name[::stack_symbol_type::operator= (const 
stack_symbol_type& that)
@@ -603,6 +602,7 @@
     location = that.location;])[
     return *this;
   }
+#endif
 
 
   template <typename Base>

reply via email to

[Prev in Thread] Current Thread [Next in Thread]