Default move constructor/assignment

Discussion in 'C++' started by Juha Nieminen, Apr 4, 2012.

  1. I can't find any info about this with google.

    This is based on the behavior of clang++ (version 3.1), and my question
    would be if this behavior is indeed how the C++11 standard defines it.

    1) Classes do not have default move constructors and assignment
    operators. They must always be explicitly declared. Is this correct?

    2) Seemingly, if you declare a move constructor for a class, that
    makes the copy assignment operator explicitly deleted. Is this correct?
    (Does it also make the move assignment operator explicitly deleted?)

    3) If the members of the class have their own move constructors and
    assignment operators, it is enough to declare a default move constructor
    and assignment for the class, like:

    class MyClass
    {
    public:
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;

    private:
    AnotherClass aMember;
    };

    If done like this, and if 'AnotherClass' has a move constructor and
    assignment operator, those will be called by the defaulted ones. Is this
    also correct?

    4) In the implementation of a move assignment operator, is it necessary
    to check for an assignment to itself? I can't think of a situation where
    a temporary could be assigned to itself.
    Juha Nieminen, Apr 4, 2012
    #1
    1. Advertising

  2. Juha Nieminen <> wrote:
    > I can't find any info about this with google.


    Seemingly I have encountered such cutting-edge C++11 technology that
    nobody even here seems to know the answers... :)

    Well, I'll just have to trust that clang++ is correct and
    standard-conforming.
    Juha Nieminen, Apr 5, 2012
    #2
    1. Advertising

  3. Juha Nieminen

    Miles Bader Guest

    Juha Nieminen <> writes:
    >> I can't find any info about this with google.

    >
    > Seemingly I have encountered such cutting-edge C++11 technology
    > that nobody even here seems to know the answers... :)


    FWIW, my memory is that the compiler should make default move
    constructors _but_ will avoid doing so if you define _any_ explicit
    move _or_ copy constructors (and similarly, an explicit move
    constructor will suppress default copy constructors).

    [I believe the "suppresses everything" behavior was a lateish change
    in the standard, and caused some problems compiling libstdc++ with
    clang at some point when clang had been updated to reflect the change,
    but libstdc++ had not... but my impression is that both gcc and clang
    are both up-to-date now in their latest releases.]

    But that's just my vague memory, and I have no links or tests at hand
    to back it up, so take it with a large grain of salt ...

    -miles

    --
    Happiness, n. An agreeable sensation arising from contemplating the misery of
    another.
    Miles Bader, Apr 5, 2012
    #3
  4. Juha Nieminen

    Guest

    On Thursday, April 5, 2012 1:24:32 AM UTC-5, Juha Nieminen wrote:
    > Juha Nieminen <> wrote:
    > > I can't find any info about this with google.

    >
    > Seemingly I have encountered such cutting-edge C++11 technology that
    > nobody even here seems to know the answers... :)
    >
    > Well, I'll just have to trust that clang++ is correct and
    > standard-conforming.



    Shalom

    The following is from Howard Hinnant in May 2011.

    The rules for an implicitly generated move constructor are:

    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.
    The rules for implicitly generated move assignment operators follows the pattern above.

    The rules for when a copy constructor is implicitly generated have changed slightly!

    If the class definition does not explicitly declare a copy constructor, oneis declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The lattercase is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

    And similarly for the copy assignment operator:

    If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor.

    Bottom line: The rule of 3 is now the rule of 5. You can either ignore all 5 (if the default behavior works for you), or you need to think about (and likely define) all 5:

    1.copy constructor
    2.copy assignment
    3.move constructor
    4.move assignment
    5.destructor

    -----------------------------------------------------------
    I'm not sure how up to date this is. I recall Dave Abrahams
    commenting on this subject on cppnext also.

    Brian Wood
    Ebenezer Enterprises
    http://webEbenezer.net
    , Apr 5, 2012
    #4
  5. wrote:
    > ???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.


    So it's not necessary to explicitly declare a move constructor or
    assignment operator eg. for a struct having movable objects as members?

    Now that I explicitly test this with clang++, it indeed seems to be so.
    I'm not sure now why in the application I was making I was not getting
    this behavior (iow. the move constructors of the members were not being
    called, and instead the copy constructors were, unless I explicitly
    declared the default move constructor of the containing class.)
    Juha Nieminen, Apr 5, 2012
    #5
  6. Juha Nieminen

    Guest

    On Thursday, April 5, 2012 4:15:24 AM UTC-5, Juha Nieminen wrote:
    > So it's not necessary to explicitly declare a move constructor or
    > assignment operator eg. for a struct having movable objects as members?


    Right. Here's the link to the cpp-next article I mentioned --
    http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/#more-3419

    >
    > Now that I explicitly test this with clang++, it indeed seems to be so.
    > I'm not sure now why in the application I was making I was not getting
    > this behavior (iow. the move constructors of the members were not being
    > called, and instead the copy constructors were, unless I explicitly
    > declared the default move constructor of the containing class.)


    Perhaps adding a call to ::std::move would be needed? Post some
    code.
    , Apr 5, 2012
    #6
  7. wrote:
    > On Thursday, April 5, 2012 4:15:24 AM UTC-5, Juha Nieminen wrote:
    >> So it's not necessary to explicitly declare a move constructor or
    >> assignment operator eg. for a struct having movable objects as members?

    >
    > Right. Here's the link to the cpp-next article I mentioned --
    > http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/#more-3419


    I'm glad that move constructors are implicitly generated by the
    compiler at least on simple cases because if they weren't, that would
    *severely* limit the usefulness of having them in the first place.

    For example, you would benefit from move constructors when using eg.
    a std::vector directly, but immediately when you put that std::vector
    inside a simple struct, you would lose those benefits (unless you went
    into the trouble of explicitly declaring the move constructor).

    I didn't really get what kind of code do these implicit move constructors
    break, but I'm pretty certain that the benefits greatly outweight the
    disadvantages.
    Juha Nieminen, Apr 6, 2012
    #7
  8. Juha Nieminen

    Guest

    On Friday, April 6, 2012 12:29:05 AM UTC-5, Juha Nieminen wrote:
    > I'm glad that move constructors are implicitly generated by the
    > compiler at least on simple cases because if they weren't, that would
    > *severely* limit the usefulness of having them in the first place.
    >
    > For example, you would benefit from move constructors when using eg.
    > a std::vector directly, but immediately when you put that std::vector
    > inside a simple struct, you would lose those benefits (unless you went
    > into the trouble of explicitly declaring the move constructor).
    >
    > I didn't really get what kind of code do these implicit move constructors
    > break, but I'm pretty certain that the benefits greatly outweight the
    > disadvantages.


    Yeah, I pretty much agree.

    http://www.velocityreviews.com/foru...ants-and-implicit-move-constructors-c-0x.html
    , Apr 7, 2012
    #8
  9. On Apr 7, 2:26 am, wrote:
    > On Friday, April 6, 2012 12:29:05 AM UTC-5, Juha Nieminen wrote:
    > >   I'm glad that move constructors are implicitly generated by the
    > > compiler at least on simple cases because if they weren't, that would
    > > *severely* limit the usefulness of having them in the first place.

    >
    > >   For example, you would benefit from move constructors when using eg..
    > > a std::vector directly, but immediately when you put that std::vector
    > > inside a simple struct, you would lose those benefits (unless you went
    > > into the trouble of explicitly declaring the move constructor).

    >
    > >   I didn't really get what kind of code do these implicit move constructors
    > > break, but I'm pretty certain that the benefits greatly outweight the
    > > disadvantages.

    >
    > Yeah, I pretty much agree.
    >
    > http://www.velocityreviews.com/forums/t730954-class-invariants-and-im...


    I agree too. However it is good to be aware of the cases that do
    break, so you can learn to recognize them, and learn how to fix them.

    The most convincing case I've seen to date is this one:

    http://groups.google.com/group/comp.lang.c .moderated/browse_thread/thread/6b158b62e7debf05#

    The code shown doesn't break. And the original code shown has
    incorrect move members. But nevertheless it is a reasonable example
    of code that might break when moving from C++03 to C++11. To break it
    you have to dress it up a little more:

    #include <cassert>
    #include <vector>
    #include <iostream>

    typedef unsigned char Byte;
    typedef std::vector<Byte> ImageVec;

    class Image
    {
    ImageVec m_data;
    size_t m_width;
    size_t m_height;
    public:
    Image(): m_width(0), m_height(0) {}
    Image(Byte* img_data, size_t width, size_t height):
    m_data(img_data, img_data + width * height * 3),
    m_width(width),
    m_height(height) {
    }

    Byte& operator()(size_t i, size_t j, size_t k)
    {
    return m_data[3*m_width*i + 3*j + k];
    }

    friend
    std::eek:stream& operator<<(std::eek:stream& os, const Image& img)
    {
    os << "{\n";
    if (img.m_height > 0)
    {
    os << " {";
    if (img.m_width > 0)
    {
    os << '{';
    os << static_cast<unsigned>(img.m_data[0]) << ','
    << static_cast<unsigned>(img.m_data[1]) << ','
    << static_cast<unsigned>(img.m_data[2]);
    os << '}';
    for (size_t j = 1; j < img.m_width; ++j)
    {
    os << ",{";
    os << static_cast<unsigned>(img.m_data[3*j]) <<
    ','
    << static_cast<unsigned>(img.m_data[3*j+1]) <<
    ','
    << static_cast<unsigned>(img.m_data[3*j+2]);
    os << '}';
    }
    }
    os << "}";
    for (size_t i = 1; i < img.m_height; ++i)
    {
    os << ",\n {";
    if (img.m_width > 0)
    {
    os << '{';
    os <<
    static_cast<unsigned>(img.m_data[3*img.m_width*i]) << ','
    <<
    static_cast<unsigned>(img.m_data[3*img.m_width*i+1]) << ','
    <<
    static_cast<unsigned>(img.m_data[3*img.m_width*i+2]);
    os << '}';
    for (size_t j = 1; j < img.m_width; ++j)
    {
    os << ",{";
    os <<
    static_cast<unsigned>(img.m_data[3*img.m_width*i+3*j]) << ','
    <<
    static_cast<unsigned>(img.m_data[3*img.m_width*i+3*j+1]) << ','
    <<
    static_cast<unsigned>(img.m_data[3*img.m_width*i+3*j+2]);
    os << '}';
    }
    }
    os << "}";
    }
    }
    return os << "\n}\n";
    }

    friend
    bool
    operator==(const Image& x, const Image& y)
    {
    return x.m_width == y.m_width && x.m_height == y.m_height &&
    x.m_data == y.m_data;
    }

    friend
    bool
    operator!=(const Image& x, const Image& y)
    {
    return !(x == y);
    }
    };

    Image
    get_Image()
    {
    Byte img_data[][6][3] =
    {
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
    };
    return Image(reinterpret_cast<Byte*>(img_data), 6, 4);
    };

    int main()
    {
    std::vector<Image> v;
    v.push_back(get_Image());
    v.push_back(get_Image());
    v.push_back(get_Image());
    std::cout << v.back() << '\n';
    v.back()(0, 0, 0) = 0;
    std::vector<Image>::iterator j = std::unique(v.begin(), v.end());
    assert(j == v.begin() + 2);
    std::cout << v.back() << '\n';
    }

    The key to why this code breaks is:

    1. It's ostream<<() friend assumes that m_data.size() == 3 * m_width
    * m_height, and indeed that is a good assumption in C++03.

    2. Client code actually inspects (prints out) a value that will be
    moved from in C++11. It does this by calling std::unique on a
    sequence of Images, which ends up moving from Images at the end of the
    sequence (in order to "remove" duplicates).

    In C++03 this program prints out:

    {
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
    }

    {
    {{0,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
    }

    But when recompiled for C++11, it prints out (for me):

    {
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
    }

    {
    Segmentation fault: 11

    Now if main() had used unique in the idiomatic way:

    v.erase(std::unique(v.begin(), v.end()), v.end());

    there would have been no crash. It is only because main used the
    moved-from value at v.back() in a way that required Image's invariant
    to hold that causes the crash.

    The easiest way to fix this is to default the copy members:

    Image(const Image&) = default;
    Image& operator=(const Image&) = default;

    Just doing this inhibits auto-generation of the move members, and then
    the code again prints out:

    {
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
    }

    {
    {{0,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
    }

    If you want to properly add the move members, you can't default them,
    as the defaults break the invariant as discussed above. The proper
    move members must set the source to the default constructed
    (resourceless) state:

    Image(Image&& img)
    : m_data(std::move(img.m_data)),
    m_width(img.m_width),
    m_height(img.m_height)
    {
    img.m_data.clear();
    img.m_width = 0;
    img.m_height = 0;
    }

    Image& operator=(Image&& img)
    {
    m_data = std::move(img.m_data);
    m_width = img.m_width;
    m_height = img.m_height;
    img.m_data.clear();
    img.m_width = 0;
    img.m_height = 0;
    return *this;
    }

    And now the code prints out:

    {
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}},
    {{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
    }

    {

    }

    Howard
    Howard Hinnant, Apr 7, 2012
    #9
  10. Juha Nieminen

    Guest

    On Saturday, April 7, 2012 12:34:01 PM UTC-5, Howard Hinnant wrote:
    >
    > I agree too. However it is good to be aware of the cases that do
    > break, so you can learn to recognize them, and learn how to fix them.
    >


    Right.

    > The most convincing case I've seen to date is this one:
    >


    Thanks for the detailed explanation.


    > Image(Image&& img)
    > : m_data(std::move(img.m_data)),


    I recall something Anthony Williams wrote about the
    members of an rvalue reference being rvalue references
    and not needing to use std::move there. Perhaps you
    use it as a reminder of what is happening and not
    because it's really needed?
    , Apr 7, 2012
    #10
  11. Juha Nieminen

    Guest

    On Saturday, April 7, 2012 5:59:28 PM UTC-5, wrote:
    > On Saturday, April 7, 2012 12:34:01 PM UTC-5, Howard Hinnant wrote:
    > >
    > > I agree too. However it is good to be aware of the cases that do
    > > break, so you can learn to recognize them, and learn how to fix them.
    > >

    >
    > Right.
    >
    > > The most convincing case I've seen to date is this one:
    > >

    >
    > Thanks for the detailed explanation.
    >
    >
    > > Image(Image&& img)
    > > : m_data(std::move(img.m_data)),

    >
    > I recall something Anthony Williams wrote about the
    > members of an rvalue reference being rvalue references
    > and not needing to use std::move there. Perhaps you
    > use it as a reminder of what is happening and not
    > because it's really needed?


    I read the thread you linked to in the moderated group
    now and the std::move is needed. It's been a while since
    I read what Anthony wrote so don't know if I'm remembering
    things wrong or he was mistaken.
    , Apr 8, 2012
    #11
  12. Juha Nieminen

    SG Guest

    On 8 Apr., 00:59, woodbria... wrote:
    > On Saturday, April 7, 2012 12:34:01 PM UTC-5, Howard Hinnant wrote:
    >
    > > The most convincing case I've seen to date is this one:

    >
    > Thanks for the detailed explanation.
    >
    > >     Image(Image&& img)
    > >         : m_data(std::move(img.m_data)),

    >
    > I recall something Anthony Williams wrote about the
    > members of an rvalue reference being rvalue references
    > and not needing to use std::move there.  Perhaps you
    > use it as a reminder of what is happening and not
    > because it's really needed?


    References have no members. And `img` is an lvalue expression. So an
    explicit std::move is required to select the move constructor. But
    you can also write

    std::move(img).m_data

    because member access on an rvalue expression yields an rvalue
    expression.

    Cheers!
    SG
    SG, Apr 10, 2012
    #12
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Aire
    Replies:
    3
    Views:
    453
    Mike Wahler
    Jan 25, 2004
  2. Replies:
    9
    Views:
    942
    Alf P. Steinbach
    Mar 6, 2006
  3. Generic Usenet Account
    Replies:
    10
    Views:
    2,194
  4. puzzlecracker
    Replies:
    8
    Views:
    416
    James Kanze
    Apr 15, 2008
  5. Andrew Tomazos
    Replies:
    2
    Views:
    588
    Nobody
    Dec 12, 2011
Loading...

Share This Page