Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

members of r value references and copy constructors

338 views
Skip to first unread message

Brendan

unread,
Apr 2, 2012, 3:36:46 AM4/2/12
to
Are members of r value references also considered r values?

In particulate, consider this Image class.

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

struct Image {
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) {
}

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

Image& operator=(Image&& img) {
if (this != &img) {
m_data = img.m_data;
m_width = img.m_width;
m_height = img.m_height;
}
return *this;
}

private:
ImageVec m_data;
size_t m_width;
size_t m_height;
};

I'm wondering if the move constructor here can be expected to move the
data from img.m_data to m_data, if I need to rewrite that as:
m_data(std::move(img.m_data))

Similarly, do I need to use std::move in the move assignment operator.

Finally, I want to clarify my understanding of when implicit move
constructors are generated. My understanding is that this changed
during the standardization process. Could someone give a statement on
exactly when implicit move constructors are generated in the final
draft, perhaps with reference to section?

My understanding is that implicit move constructors would be generated
in the scenario above. I'm actually kind of perturbed by that, because
while the implicit copy constructor would be fine, the implicit move
constructor would leave the source object in an invalid state.

Thanks,
Brendan Miller


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

SG

unread,
Apr 2, 2012, 1:22:25 PM4/2/12
to
On Apr 2, 9:36 am, Brendan wrote:
> Are members of r value references also considered r values?

References have no members. If you mean the members of an object
that's an rvalue expression: yes, the accessed member will also be an
rvalue expression. Example:

struct foo {
string s;
};

void bar(string const&); // #1
void bar(string &&); // #2

int main() {
bar(foo().s); // --> #2
}

> In particulate, consider this Image class.
>
> typedef unsigned char Byte;
> typedef std::vector<Byte> ImageVec;
>
> struct Image {
> [...]
> Image(Image&& img):
> m_data(img.m_data),
> m_width(img.m_width),
> m_height(img.m_height){
> img.m_width = 0;
> img.m_height = 0;
> }

Oh, I see, you're talking about NAMED rvalue references ...

> I'm wondering if the move constructor here can be expected to move the
> data from img.m_data to m_data, if I need to rewrite that as:
> m_data(std::move(img.m_data))

img is a named reference. As such it's an lvalue expression.
img.m_data will therefore also be an lvalue expression. So, either use
std::move here or simply don't write any copy/move operations and the
destructor yourself.

> Similarly, do I need to use std::move in the move assignment operator.

While the answer to this question is "No", the answer to the question
you actually have in mind is "Yes.". Again: the name of a reference is
always an lvalue expression. Only an UNNAMED rvalue reference
(returned from a function) is an rvalue.

Anyhow, it is perfectly reasonable to use a "unified assignment
operator" in many cases:

Image& Image&::operator=(Image temp_copy)
{
this->swap(temp_copy);
return *this;
}

You don't see any std::move here. So, it's not necessary ;-)
Technically, this is considered a copy assignment operator (judging by
the definition of copy assignment in the C++ standard). But in case
your class is cheaply movable, and you have a cheap swap function,
this kind of operator serves both purposes (copy assign and move
assign with just an additional but cheap move to create temp_copy).

> Finally, I want to clarify my understanding of when implicit move
> constructors are generated. My understanding is that this changed
> during the standardization process. Could someone give a statement on
> exactly when implicit move constructors are generated in the final
> draft, perhaps with reference to section?

There are 6 special member functions. Without default constructor,
this leaves the following 5:

* copy ctor
* move ctor
* copy assign
* move assign
* dtor

Move operations are generated if all data members support the move
operation in question and the user DID NOT declare ANY of these 5
special member functions.

The same is true for generating copy operations with the EXCEPTION
that a user-declared copy operation or user-declared dtor DOES NOT YET
stop the compiler from generating remaining copy operations. This
exception is for C++98 compatibility. But it is already deprecated and
code that relies on this exception should be changed to make use of C+
+2011's "operation=default;" declarations so that it'll continue to
work in C++1y mode ;-)

Builtin types are considered to be movable by copying.

Cheers!
SG

Daniel Krügler

unread,
Apr 2, 2012, 3:17:07 PM4/2/12
to
Am 02.04.2012 09:36, schrieb Brendan:
> Are members of r value references also considered r values?

I have difficulties understanding this question. An rvalue reference
itself has no members, so I must assume you speak of the properties of
some "expression". In this case, it is relevant whether the expression
is an lvalue expression or an rvalue expression. Note that it is only of
secondary relevance whether we speak of a variable that is an rvalue
reference. Details see below.
You need to use std::move to convert all your lvalues to rvalues. Within
the move-constructor and the move-assignment the expression "img" is an
lvalue. It does not matter what the underlying type of the declared
variable is. Now the follow-up question to clarify is: What is the
value-category of a member access expression (like "img.m_data") for a
variable of class type? Here we look at 5.2.5 [expr.ref] p4 and try to
deduce the value category of an expression E1.E2:

"If E2 is declared to have type “reference to T,†then E1.E2 is an lvalue;"

Is m_data a reference type? No, so we proceed with bullet 2 of
aforementioned paragraph:

"If E1 is an lvalue, then E1.E2 is an lvalue; if E1 is an xvalue, then
E1.E2 is an xvalue; otherwise, it is a prvalue."

E1 is an lvalue (it is a variable with a name), so E1.E2 is also an lvalue.

> Finally, I want to clarify my understanding of when implicit move
> constructors are generated. My understanding is that this changed
> during the standardization process.

Your assumption is correct.

> Could someone give a statement on
> exactly when implicit move constructors are generated in the final
> draft, perhaps with reference to section?

12.8 [class.copy] p9:

"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."

now you need to know when a move constructor will be deleted. This is
described in p11:

"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.
"

> My understanding is that implicit move constructors would be generated
> in the scenario above.

You mean, if you would not have user-provided the move constructor and
the move assignment operator? In this case we look at your data members,
which are std::vector<unsigned char> and std::size_t types, which are
all move-constructible. There is nothing in class Image that would
prevent the implicit declaration of the move-constructor and the
move-assignment operator.

> I'm actually kind of perturbed by that, because
> while the implicit copy constructor would be fine, the implicit move
> constructor would leave the source object in an invalid state.

Yes, this is one of the examples where the compiler-declared
move-constructor does the wrong thing.

HTH & Greetings from Bremen,

Daniel Krügler

Gene Bushuyev

unread,
Apr 2, 2012, 6:02:43 PM4/2/12
to
> ...
> My understanding is that implicit move constructors would be generated
> in the scenario above. I'm actually kind of perturbed by that, because
> while the implicit copy constructor would be fine, the implicit move
> constructor would leave the source object in an invalid state.

How did you conclude that the implicitly defined move constructor
leaves the source in an invalid state? The only requirement for the
moved-from object is that it can be safely destroyed (the object will
be gone after move). You didn't provide any destructor and the
compiler generated one will safely destroy the class. But if you did
provide a destructor with something unusual, like asserting a certain
invariant, then compiler wouldn't generate implicit move operations.

Dave Abrahams

unread,
Apr 3, 2012, 12:28:40 AM4/3/12
to
Simple: there was an invariant that m_data has m_width*m_height*3
elements. That invariant would be violated by the implicitly-generated
move constructor. You might read my article about this problem at
http://cpp-next.com/archive/2010/10/implicit-move-must-go/

> The only requirement for the moved-from object is that it can be
> safely destroyed (the object will be gone after move).

No, that's wrong even if you take a naïve view of this problem. The
object must also be assignable, since standard algorithms and containers
routinely assign over moved-from objects.

But that's the naïve view. In fact, the requirement for a moved-from
object is the same as for any other object: it must satisfy the
invariants of the class. If the state doesn't satisfy the invariant,
the supposed invariant is not what it claims to be and the program is
broken. Implicitly generating move operations can introduce states that
were formerly impossible to achieve. When new states outside the stated
class invariant are introduced, code that depends on the invariant will
break. Sometimes that code exists already, and sometimes it has yet to
be written, but the code has diverged from its specification and the
program is broken.

> You didn't provide any destructor and the compiler generated one will
> safely destroy the class. But if you did provide a destructor with
> something unusual, like asserting a certain invariant, then compiler
> wouldn't generate implicit move operations.

The fact that there's no destructor is irrelevant. Moved-from objects
can be observed from other places, e.g. after an exception is thrown, or
after std::remove is called. See "Tweak #1" in the aforementioned
article.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
0 new messages