89

I'm a simple programmer. My class members variables most often consists of POD-types and STL-containers. Because of this I seldom have to write assignment operators or copy constructors, as these are implemented by default.

Add to this, if I use std::move on objects not movable, it utilizes the assignment-operator, meaning std::move is perfectly safe.

As I'm a simple programmer, I'd like to take advantage of the move-capabilities without adding a move constructor/assignment operator to every class I write, as the compiler could simply implemented them as "this->member1_ = std::move(other.member1_);..."

But it doesn't (at least not in Visual 2010), is there any particular reason for this?

More importantly; is there any way to get around this?

Update: If you look down at GManNickG's answer he provides a great macro for this. And if you didn't know, if you implement move-semantics you can remove the swap member function.

Jonas
  • 97,987
  • 90
  • 271
  • 355
Viktor Sehr
  • 12,172
  • 3
  • 52
  • 78

4 Answers4

76

The implicit generation of move constructors and assignment operators has been contentious and there have been major revisions in recent drafts of the C++ Standard, so currently available compilers will likely behave differently with respect to implicit generation.

For more about the history of the issue, see the 2010 WG21 papers list and search for "mov"

The current specification (N3225, from November) states (N3225 12.8/8):

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor, and

  • X does not have a user-declared copy assignment operator,

  • X does not have a user-declared move assignment operator,

  • X does not have a user-declared destructor, and

  • the move constructor would not be implicitly defined as deleted.

There is similar language in 12.8/22 specifying when the move assignment operator is implicitly declared as defaulted. You can find the complete list of changes made to support the current specification of implicit move generation in N3203: Tightening the conditions for generating implicit moves , which was based largely on one of the resolutions proposed by Bjarne Stroustrup's paper N3201: Moving right along.

Community
  • 1
  • 1
James McNellis
  • 327,682
  • 71
  • 882
  • 954
  • 4
    I wrote a small article with some diagrams describing the relationships for implicit (move)constructor/assignment here: http://mmocny.wordpress.com/2010/12/09/implicit-move-wont-go/ – mmocny Apr 26 '11 at 14:35
  • Ugh so whenever I have to define blank destructors in polymorphic base classes just for the sake of specifying it as virtual, I have to explicitly define the move constructor and assignment operator as well :(. – someguy Aug 10 '11 at 15:01
  • @James McNellis: That's something I previously tried, but the compiler didn't seem to like it. I was going to post the error message in this very reply, but after trying to reproduce the error, I realized that it mentions that it `cannot be defaulted *in the class body*`. So, I defined the destructor outside and it worked :). I find it a little strange, though. Does anyone have an explanation? Compiler is gcc 4.6.1 – someguy Aug 10 '11 at 17:01
  • @nawaz "mov" is intentional: there is a paper entitled "Moving Right Along," which won't be found by a search for "move." – James McNellis Sep 22 '13 at 06:14
  • @JamesMcNellis: Ohh, in that case "moving" is a better term otherwise someone else might come and change "mov" to "move" again, thinking it is a typo. :-) I would prefer the title name itself : "Moving Right Along", because "mov" finds 16 results; which one(s) you intended? All, few of them or exactly one? – Nawaz Sep 22 '13 at 06:42
  • @nawaz All of them. Some use "move" and others use "moving". Just leave it as is, please. – James McNellis Sep 22 '13 at 06:56
  • 3
    Maybe we could get an update to this answer now that C++11 is ratified? Curious what behaviors won. – Joseph Garvin Feb 24 '14 at 23:12
  • What does the current standard say on this? – isarandi Jun 04 '14 at 00:04
  • @someguy - Perhaps I'm missing something, but I do not understand why do you "have to explicitly define the move constructor and assignment operator as well" in case of defining the dtor in your base class. (if the default move ctor and assignment operator are provided, they actually do copy semantics) ? – Guy Avraham Sep 13 '18 at 12:57
  • 2
    @Guy Avraham: I think what I was saying (it's been 7 years) is that if I have a user-declared destructor (even an empty virtual one), no move constructor will be declared implicitly as defaulted. I suppose that would result in copy semantics? (I haven't touched C++ in years.) James McNellis then commented that `virtual ~D() = default;` should work and still allow an implicit move constructor. – someguy Sep 17 '18 at 11:48
13

Implicitly generated move constructors have been considered for the standard, but can be dangerous. See Dave Abrahams's analysis.

In the end, however, the standard did include implicit generation of move constructors and move assignment operators, though with a fairly substantial list of limitations:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
— X does not have a user-declared copy constructor,
— X does not have a user-declared copy assignment operator,
— X does not have a user-declared move assignment operator,
— X does not have a user-declared destructor, and
— the move constructor would not be implicitly defined as deleted.

That's not quite all there is to the story though. A ctor can be declared, but still defined as deleted:

An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (8.4.3) if X has:

— a variant member with a non-trivial corresponding constructor and X is a union-like class,
— a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3), as applied to M’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
— a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3), as applied to B’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
— any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor,
— for the copy constructor, a non-static data member of rvalue reference type, or
— for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.

Community
  • 1
  • 1
Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
  • The current working draft does allow implicit move generation under certain conditions and I think the resolution largely addresses Abrahams's concerns. – James McNellis Jan 27 '11 at 18:36
  • I am not sure I understood what move may break in the example between Tweak 2 and Tweak 3. Could you explain it ? – Matthieu M. Jan 27 '11 at 19:25
  • @Matthieu M.: both Tweak 2 and Tweak 3 are broken, and in pretty similar ways, really. In Tweak 2, there are private members with invariants that can be broken by the move ctor. In Tweak 3, the class doesn't have private members *itself*, but since it uses private inheritance, the public and protected members of the base become private members of the derived, leading to the same problem. – Jerry Coffin Jan 27 '11 at 19:34
  • 1
    I didn't really understand how the move constructor would break the class invariant in `Tweak2`. I suppose it's got something to do with the fact that the `Number` would be moved and the `vector` would be copied... but I am not sure :/ I do understand the problem would cascade to `Tweak3`. – Matthieu M. Jan 27 '11 at 19:46
  • The link you gave seems to be dead? – Wolf Jun 27 '14 at 10:33
  • There's a paper at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3153.htm which appears to be the same analysis. – Ismael Aug 28 '14 at 18:50
8

(as for now, I'm working on a stupid macro...)

Yeah, I went that route too. Here's your macro:

// detail/move_default.hpp
#ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP
#define UTILITY_DETAIL_MOVE_DEFAULT_HPP

#include <boost/preprocessor.hpp>

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE(pR, pData, pBase) pBase(std::move(pOther))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE(pR, pData, pBase) pBase::operator=(std::move(pOther));

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR(pR, pData, pMember) pMember(std::move(pOther.pMember))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT(pR, pData, pMember) pMember = std::move(pOther.pMember);

#define UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        ,                                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)                                                   \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#endif

// move_default.hpp
#ifndef UTILITY_MOVE_DEFAULT_HPP
#define UTILITY_MOVE_DEFAULT_HPP

#include "utility/detail/move_default.hpp"

// move bases and members
#define UTILITY_MOVE_DEFAULT(pT, pBases, pMembers) UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)

// base only version
#define UTILITY_MOVE_DEFAULT_BASES(pT, pBases) UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)

// member only version
#define UTILITY_MOVE_DEFAULT_MEMBERS(pT, pMembers) UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)

#endif

(I've removed the real comments, which are length and documentary.)

You specify the bases and/or members in your class as a preprocessor list, for example:

#include "move_default.hpp"

struct foo
{
    UTILITY_MOVE_DEFAULT_MEMBERS(foo, (x)(str));

    int x;
    std::string str;
};

struct bar : foo, baz
{
    UTILITY_MOVE_DEFAULT_BASES(bar, (foo)(baz));
};

struct baz : bar
{
    UTILITY_MOVE_DEFAULT(baz, (bar), (ptr));

    void* ptr;
};

And out comes a move-constructor and move-assignment operator.

(As an aside, if anyone knows how I could combine the details into one macro, that would be swell.)

GManNickG
  • 459,504
  • 50
  • 465
  • 534
  • Thank you very much, mine is pretty similar, except I had to pass the number of member variables as an argument (which really sucks). – Viktor Sehr Jan 28 '11 at 09:21
  • 1
    @Viktor: No problem. If it's not too late, I think you should mark one of the other answers as accepted. Mine was more of a "by the way, here's a way" and not an answer to your real question. – GManNickG Jan 28 '11 at 09:42
  • 1
    If I'm reading your macro correctly, then as soon as your compiler implements defaulted move members, your examples above will become non-copyable. Implicit generation of copy members is inhibited when there are explicitly declared move members present. – Howard Hinnant Jan 28 '11 at 14:13
  • @Howard: That's okay, it's a temporary solution until then. :) – GManNickG Jan 28 '11 at 14:15
  • GMan: This macro adds moveconstructor\assign if you have a swap function: – Viktor Sehr Feb 03 '12 at 08:33
  • // Requires: swap member function, can be used on non-copyable objects // Implements: move-assignment and move-constructor #define IMPLEMENTS_MOVEABLE() \ my_type(my_type&& other) { this->swap(other); } \ my_type& operator=(my_type&& other) { this->swap(other); return *this; } – Viktor Sehr Feb 03 '12 at 08:33
  • @ViktorSehr: That only works when `my_type`'s members all correctly default-construct themselves. If you had an `int` member, for example, it would be uninitialized then swapped, causing UB. – GManNickG Feb 03 '12 at 08:37
  • @GManNickG: Your macro does a better job aswell, after some testing I found out the macro I provided does more "moves" of members than yours . Great job! – Viktor Sehr Mar 04 '12 at 19:27
  • You forgot #pragma once in move_default.hpp thou =) – Viktor Sehr Mar 04 '12 at 19:28
  • @ViktorSehr: I don't use `#pragma once`, just header guards. – GManNickG Mar 04 '12 at 21:03
  • @GManNickG: Rewrote your macro to also create copy-constructor, regular assignment and operator==\operator!=. Using it everywhere now =) Makes my classes so much cleaner and less error phrone. Thanks! – Viktor Sehr Mar 06 '12 at 20:57
4

VS2010 doesn't do it because they weren't Standard at the time of implementation.

Puppy
  • 138,897
  • 33
  • 232
  • 446