Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #15206 > unrolled thread

Default move constructor/assignment

Started byJuha Nieminen <nospam@thanks.invalid>
First post2012-04-04 09:21 +0000
Last post2012-04-10 02:21 -0700
Articles 12 — 5 participants

Back to article view | Back to comp.lang.c++


Contents

  Default move constructor/assignment Juha Nieminen <nospam@thanks.invalid> - 2012-04-04 09:21 +0000
    Re: Default move constructor/assignment Juha Nieminen <nospam@thanks.invalid> - 2012-04-05 06:24 +0000
      Re: Default move constructor/assignment Miles Bader <miles@gnu.org> - 2012-04-05 16:48 +0900
      Re: Default move constructor/assignment woodbrian77@gmail.com - 2012-04-05 01:26 -0700
        Re: Default move constructor/assignment Juha Nieminen <nospam@thanks.invalid> - 2012-04-05 09:15 +0000
          Re: Default move constructor/assignment woodbrian77@gmail.com - 2012-04-05 09:35 -0700
            Re: Default move constructor/assignment Juha Nieminen <nospam@thanks.invalid> - 2012-04-06 05:29 +0000
              Re: Default move constructor/assignment woodbrian77@gmail.com - 2012-04-06 23:26 -0700
                Re: Default move constructor/assignment Howard Hinnant <howard.hinnant@gmail.com> - 2012-04-07 10:34 -0700
                  Re: Default move constructor/assignment woodbrian77@gmail.com - 2012-04-07 15:59 -0700
                    Re: Default move constructor/assignment woodbrian77@gmail.com - 2012-04-07 18:58 -0700
                    Re: Default move constructor/assignment SG <s.gesemann@gmail.com> - 2012-04-10 02:21 -0700

#15206 — Default move constructor/assignment

FromJuha Nieminen <nospam@thanks.invalid>
Date2012-04-04 09:21 +0000
SubjectDefault move constructor/assignment
Message-ID<4f7c129b$0$4386$7b1e8fa0@news.nbl.fi>
  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.

[toc] | [next] | [standalone]


#15224

FromJuha Nieminen <nospam@thanks.invalid>
Date2012-04-05 06:24 +0000
Message-ID<4f7d3aa0$0$3114$7b1e8fa0@news.nbl.fi>
In reply to#15206
Juha Nieminen <nospam@thanks.invalid> 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.

[toc] | [prev] | [next] | [standalone]


#15227

FromMiles Bader <miles@gnu.org>
Date2012-04-05 16:48 +0900
Message-ID<87k41uy5yk.fsf@catnip.gol.com>
In reply to#15224
Juha Nieminen <nospam@thanks.invalid> 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.

[toc] | [prev] | [next] | [standalone]


#15228

Fromwoodbrian77@gmail.com
Date2012-04-05 01:26 -0700
Message-ID<28811616.820.1333614389405.JavaMail.geo-discussion-forums@yno37>
In reply to#15224
On Thursday, April 5, 2012 1:24:32 AM UTC-5, Juha Nieminen wrote:
> Juha Nieminen <nospam@thanks.invalid> 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, one is 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 latter case 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

[toc] | [prev] | [next] | [standalone]


#15229

FromJuha Nieminen <nospam@thanks.invalid>
Date2012-04-05 09:15 +0000
Message-ID<4f7d62ac$0$3066$7b1e8fa0@news.nbl.fi>
In reply to#15228
woodbrian77@gmail.com 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.)

[toc] | [prev] | [next] | [standalone]


#15231

Fromwoodbrian77@gmail.com
Date2012-04-05 09:35 -0700
Message-ID<24078035.279.1333643732405.JavaMail.geo-discussion-forums@ynes7>
In reply to#15229
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.

[toc] | [prev] | [next] | [standalone]


#15232

FromJuha Nieminen <nospam@thanks.invalid>
Date2012-04-06 05:29 +0000
Message-ID<4f7e7f21$0$4360$7b1e8fa0@news.nbl.fi>
In reply to#15231
woodbrian77@gmail.com 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.

[toc] | [prev] | [next] | [standalone]


#15237

Fromwoodbrian77@gmail.com
Date2012-04-06 23:26 -0700
Message-ID<19996601.695.1333779961722.JavaMail.geo-discussion-forums@yndg18>
In reply to#15232
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-implicit-move-constructors-c-0x.html

[toc] | [prev] | [next] | [standalone]


#15240

FromHoward Hinnant <howard.hinnant@gmail.com>
Date2012-04-07 10:34 -0700
Message-ID<74febb32-d75d-44d2-956f-d205d5659220@i18g2000vbx.googlegroups.com>
In reply to#15237
On Apr 7, 2:26 am, woodbria...@gmail.com 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::ostream& operator<<(std::ostream& 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

[toc] | [prev] | [next] | [standalone]


#15241

Fromwoodbrian77@gmail.com
Date2012-04-07 15:59 -0700
Message-ID<8544443.2471.1333839568503.JavaMail.geo-discussion-forums@ynpp8>
In reply to#15240
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?

[toc] | [prev] | [next] | [standalone]


#15242

Fromwoodbrian77@gmail.com
Date2012-04-07 18:58 -0700
Message-ID<358355.525.1333850294795.JavaMail.geo-discussion-forums@ynbv36>
In reply to#15241
On Saturday, April 7, 2012 5:59:28 PM UTC-5, woodb...@gmail.com 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.

[toc] | [prev] | [next] | [standalone]


#15268

FromSG <s.gesemann@gmail.com>
Date2012-04-10 02:21 -0700
Message-ID<2355fcd8-3ac7-42ff-93ec-483a98e08cff@l4g2000vbt.googlegroups.com>
In reply to#15241
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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.c++


csiph-web