0

What are disadvantages of using macro as interface of all move semantics?

I have recently read about syntax of move-related function inside a class (http://stackoverflow.com/a/4782927/ ), and in my opinion, it is quite tedious and repetitive :-

C(C&&) = default;                    // Move constructor
C& operator=(C&&) & = default;       // Move assignment operator

It is dirtier if I want to customize both move-assignment and move-constructor with the same implementation. (same problem as Copy constructor and = operator overload in C++: is a common function possible?)

Therefore, I decide to create a macro (swap-like function) to make everything concise in a single place.

#define MACRO_MOVE(C)                        \
C& operator=(C&& that) &  {                 \
     moveHack(this,&that);  return *this;    \
}                                            \
C(C&& that){                                 \
     moveHack(this,&that);                   \
}                                            \
static void moveHack(C* dst,C* src)

Here is the usage :-

class X{
    int data=42;
    MACRO_MOVE(X){
        dst->data=src->data;
        src->data=0;
    }
};

However, I haven't seen anyone do like this before.
Are there any issues I should concern?
Are there any specific disadvantages of this approach?

cppBeginner
  • 1,114
  • 9
  • 27
  • What happens when the move-assignment operator needs to free some resource? And, if it doesn't need to do that, why wouldn't the compiler-generated move-assignment operator suffice? – cdhowie Jun 05 '17 at 04:10
  • @cdhowie IMHO, move-assignment always work the same as move-constructor, so I will add such code near the line `src->data=0`. (?) – cppBeginner Jun 05 '17 at 04:12
  • 1
    That's not true. In the move constructor, you know that you have no existing resource to free. Either way, you can generally implement both as `swap(that)` provided that the move-constructor properly initializes itself to own nothing. – cdhowie Jun 05 '17 at 04:13
  • Completely unrelated to your question: I think that a signature like `foobar(C* src,C* dst)` should instead almost always be `foobar(C* dst,C* src)`. Having the destination be the first argument seems to be the common idiom in C like languages. – Michael Burr Jun 05 '17 at 04:15
  • @MichaelBurr True, and while we're there, why not `C&`? – cdhowie Jun 05 '17 at 04:15
  • @cdhowie Sound like I am misunderstanding something.... may you give a more specific example that both functions should be different please? .... "not `C&`" because `C*` make me less confuse. – cppBeginner Jun 05 '17 at 04:15
  • @Michael Burr Thank, I will fix it. – cppBeginner Jun 05 '17 at 04:16
  • They don't necessarily have to be very different, however: (1) if they are the same they are both usually implement in terms of `swap(*this, that)`, which should be a free function for ADL to work when someone else wants to swap, and (2) in order for that to work you have to properly null-initialize things in the `C(C&&)` initializer list (or chain to the default contructor to do that `C(C&&):C()`) so that the newly-move-constructed object has a properly-initialized null state to swap into the argument. – cdhowie Jun 05 '17 at 04:17
  • So at the very least, if you are going with the move=swap idiom then you need to make your macro `C(C&& that) : C() { ...` and require that the default constructor null-initialize properly. – cdhowie Jun 05 '17 at 04:19
  • @cdhowie Thank! I totally forget about (2). However, in most cases, I don't need to null-initialize it, so it is probably ok ?. (I tend to do in-line initialization i.e. at each field declaration e.g. `int data=42;`) – cppBeginner Jun 05 '17 at 04:20
  • @MichaelBurr `std::copy` puts the source first ; I think that idiom is not a thing in C++ – M.M Jun 05 '17 at 04:21
  • @cppLover If it helps, [this](http://coliru.stacked-crooked.com/a/651e74dc61edbb26) is a template you can use for a class that can be moved and copied. The places where you need to drop code are marked by `IMPL`. There's not really too much to be gained here by using a macro. And note that you only need to do this if the compiler-generated copy and move operations would not do the right thing automatically -- which they should generally do if you are using standard types and smart pointers. – cdhowie Jun 05 '17 at 04:30
  • @cdhowie Thank. I am new to C++, but notice that many ones (especially good tutorials) tend to waste some code lines as a sacrifice to the god of `std::move`. For example, in the coliru example, I waste lose around 3-5 lines for it. All of such lines looks like C++ noise, not cute, and appear the same for every class. (`C(C &&that) noexcept ... a few lines`, `D(D &&that) noexcept ... a few lines`,...) For me, it makes the whole program less readable. Is it a professional way? – cppBeginner Jun 05 '17 at 04:38
  • 1
    @cppLover Macros are [generally to be avoided](https://isocpp.org/wiki/faq/style-and-techniques#why-not-macros) as they obfuscate what's really happening, particularly to people not familiar with your code. The general rule is that macros should only be used when their use is significantly more helpful than it is harmful to clarity, and I'm not sure that's true here. I would make the opposite argument: using macros here harms readability (in my opinion) by basically *hiding* two member declarations from the reader. – cdhowie Jun 05 '17 at 04:43
  • @cdhowie Thank for dedicating your time to explain a beginner like me. It is valuable information and opinion. I will reconsider my design. Thank. :) – cppBeginner Jun 05 '17 at 04:52
  • Instead of a macro, you can use CRTP for the same purpose – Ap31 Jun 05 '17 at 05:34
  • Here, [something like that](http://ideone.com/OedgmG) - and you should be able to declare your class like `class C : public MoveClass` – Ap31 Jun 05 '17 at 06:06
  • 1
    The point is that in theory you should let the compiler generate the default move special functions, which should do the right thing if all your members already implement move semantics correctly. This in particular means that you won't ever have naked pointers owned by your class (as `data` seems to be in your example), but they'll always be wrapped in something like `unique_ptr`. That's the theory, then of course YMMV. – Matteo Italia Jun 05 '17 at 06:12
  • @Ap31 Thank, it looks promising. I still believe macro approach is little more concise, though. (especially at line 21) – cppBeginner Jun 05 '17 at 06:25
  • @Matteo Italia I understand and agree. I currently need that macro in only 5-6 classes (or less) around memory management. I just realized it after I read your comment, thank. – cppBeginner Jun 05 '17 at 06:27
  • @cppLover: mind you, I feel your pain, implementing this stuff is a PITA and I don't think that using a macro for that is any worse than CRTP. Still, consider basing it on a free `swap` function, the implementation becomes trivial and you don't have magic syntax of a function body after a macro. Also, if it's about memory management consider that often you can just use `unique_ptr` with a custom deleter (but again, YMMV, I'm just suggesting because not everybody knows of that customization point). – Matteo Italia Jun 05 '17 at 06:39

1 Answers1

0

The rule of zero is that only classes that manage resources (like memory) need an explicit move/copy assign/construction method.

You can compose storage of almost every type into self-managing resources. These resource types tend to be one of a few different variants (value pointer, unique pointer, shared pointer, etc).

Usually when move-assigning, you also want to clean up the existing state. This isn't true when move-constructing. The two implementations can share some code, but often you don't want them to.

Doing that kind of macro hackery for a few simple resource management types is a bad idea. Having complex resource management types all over the place is a bad idea. This macro hackery doens't deal with move and assignment being fundamentally different operations. Macros, in general, should only be used when they are costly to avoid.

Your macro doesn't add much utility. What it does do looks bug prone and dangerous. If you ever have to debug it, you get undebuggable code.

So there are lots of reasons not to use that macro, and few to use it.

Construction, assignment and destruction are tied together. Assignment can be viewed as is destruction + construction naively, but that is usually ineffient; and yours doesn't even do the destruction!

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524