What is a good way to eliminate the pattern associated with default / delete move / copy semantics?

Scott Meyers has a good point on the rule of zero . Basically, he stands for the default assignment / creation / move, regardless of whether they really need you. Basically, the general rule is to avoid generating a compiler for these members, mainly because they are a big source of confusion (I agree with that).

So, I was thinking about good general practice in how to define a class as movable, copyable or non-removable, not copyable. I was thinking about boost boost::noncopyable , but I don't like the idea of ​​introducing inheritance for such a functional purpose.

The only thing I can think of makes sense is to resort to macros. So I came up with something like this:

 /// Disable copy construct/assign for the given class T #define CLASS_NON_COPYABLE(T) \ T(T const&) = delete; \ T& operator=(T const&) = delete /// Disable move construct/assign for the given class T #define CLASS_NON_MOVABLE(T) \ T(T&&) = delete; \ T& operator=(T&&) = delete /// Disable both copy and move construct/assign for the given class T #define CLASS_NON_COPYABLE_OR_MOVABLE(T) \ CLASS_NON_COPYABLE(T); \ CLASS_NON_MOVABLE(T) /// Default copy move/assign #define CLASS_DEFAULT_COPYABLE(T) \ T(T const&) = default; \ T& operator=(T const&) = default /// Default move construct/assign #define CLASS_DEFAULT_MOVABLE(T) \ T(T&&) = default; \ T& operator=(T&&) = default /// Defaulted versions of both copy and move construct/assign for the given class T #define CLASS_DEFAULT_COPYABLE_OR_MOVABLE(T) \ CLASS_DEFAULT_COPYABLE(T); \ CLASS_DEFAULT_MOVABLE(T) 

And an example of how to use them:

 class foo { public: foo() = default; virtual ~foo() = default; CLASS_NON_COPYABLE(foo); CLASS_DEFAULT_MOVABLE(foo); }; int main() { foo a, b; a = b; // FAIL: can't copy; class is "non copyable" a = foo(); // OK: class is 'default movable' } 

( Live sample )

For me, this looks a lot cleaner than the alternative:

 class foo { public: foo() = default; virtual ~foo() = default; foo(foo const&) = delete; foo(foo&&) = default; foo& operator=(foo const&) = delete; foo& operator=(foo&&) = default; }; 

This is debatable, as most of the style-based issues are there, but I find the advantage in the former:

  • Macros are searchable, so you can find classes that use move / copy semantics in different ways without using a complex regular expression.
  • It is natural to #ifdef transfer / copy semantics in old compilers through the #ifdef logic, where C ++ 11 is not available (for example, CLASS_NON_MOVABLE will be inoperative).
  • It's easier to look at your eyes to see what the class is doing.
  • And it's easier to type :-)

Given that I do a lot of magic here, my soul hurts a little, and I feel the need for compulsory "SO-mail" to see if I really do it efficiently and not lose my mind. "

So, I have a few closely related questions:

  • Am I deleting templates in an ideal and efficient way?
  • Are there any better / other or more recommended methods for this. I would even be open to elegant inheritance solutions similar to what Boost provides.

Maybe I'll do it all wrong. Perhaps this whole question is doomed to failure, and I am above thoughts. I would also like to rethink all of this, just in case I went too far down the rabbit hole.

+6
source share
2 answers

I grinned and confirmed TemplateRex a good answer. At the same time, if you need to declare your destructor virtual, then you cannot just leave everything in the compiler. However, knowing the rules (and assuming the appropriate compiler), you can minimize both what you need to type and what you need to read.

In this specific example, I recommend:

 class foo { public: virtual ~foo() = default; foo() = default; foo(foo&&) = default; foo& operator=(foo&&) = default; }; 

Notes:

  • If you declare any move element, both copy instances are implicitly deleted. You can use this rule to reduce the number of boiler plates.

  • I recommend putting your data members at the top of your class, and then your special contributors immediately follow this. This contradicts many recommendations that recommend placing your data at the bottom, as it is not important to your readers. But if your reader wants to know what special members are going to do when defaulted, the reader should see your data members.

  • I recommend always ordering your declared special items in the same order. This helps (reader initiative) to understand when you have not announced a special member. I have a recommended order. But regardless of order, be consistent.

  • The order I recommend is the destructor, default constructor, copy constructor, copy assignment, constructor move, move assignment. I like this order because I consider the destructor the most important special member. This one of the features tells me about class design. I like to group the members of my copy together and my move elements together because they are often defaulted or both are deleted.

As for the instance members for this example, given the style rules above, it’s easy (at least for me) to see that they are implicitly deleted and therefore read less (dirty fingers and all this: -.))

Update

At the risk of exiting the topic, it is recommended that foo.cpp contain confirmation that you received special items:

 static_assert(std::is_nothrow_destructible<foo>{}, "foo should be noexcept destructible"); static_assert(std::has_virtual_destructor<foo>{}, "foo should have a virtual destructor"); static_assert(std::is_nothrow_default_constructible<foo>{}, "foo should be noexcept default constructible"); static_assert(!std::is_copy_constructible<foo>{}, "foo should not be copy constructible"); static_assert(!std::is_copy_assignable<foo>{}, "foo should not be copy assignable"); static_assert(std::is_nothrow_move_constructible<foo>{}, "foo should be noexcept move constructible"); static_assert(std::is_nothrow_move_assignable<foo>{}, "foo should be noexcept move assignable"); 

I added "nothrow" in some places. Delete it, if applicable, or add it to more places, if applicable. Use trivial instead, if applicable. One size is not suitable for everyone.

This combination of expressing what you intend in the headline and confirming what you said is what you got in the source, it really helps to fix the code.

Hiding this "boiler plate" under macros is worthwhile: the reader must find the definition of a macro. If you use such a macro, judge whether the advantage of the macro exceeds its value.

+7
source

@HowardHinnant has much better advice for the rule of zero:

 class foo { public: // just keep your grubby fingers off of the keyboard }; 
+8
source

Source: https://habr.com/ru/post/976230/


All Articles