Hello Daniwebers!

I was just reading This C++0x Beginner Tutorial: http://www.daniweb.com/software-development/cpp/tutorials/373787/beginning-c0x-making-a-raii-class

And I was confused by something:

"
int_vector(int_vector& aV);

Obviously, this type of constructor would allow the transfer of the resource, since the source object can be modified and put into an empty-state (or "zombie-state"). But there are two problems with the above, which make it an impractical solution. First, a temporary object, formally referred to as anrvalue, cannot be bound to a non-const reference, meaning that this constructor will not be able to move resources from a temporary object to the newly constructed object."

I have a question here: Isn't the compiler-generated copy constructor called if I used an rvalue like int_vector(int_vector(5)) and it gets a shallow copy of the temporary object just like the move constructor?

Thanks in advance,
Kimo

Recommended Answers

All 7 Replies

I also thought that initializing the object with

int_vector(<temporary object>)

would call

int_vector::int_vector(const int_vector& src)

but it doesn't. It makes a shallow copy of the temporary object instead.

I made the following test program and compiled it with g++:

#include <iostream>
#include <cstring>

class Test
{
public:
  std::string state;

  Test()
  {
    std::cout << "Default Constructor Called!" << std::endl;
  }

  Test(std::string st) : state(st) { };

  Test(const Test& src) : state("My Copy Constructor Called!") { };
};

Test GetTest()
{
  return Test("Compiler-Generated Copy Constructor Called!");
}

int main()
{
  Test test(GetTest()); /* Expected to call Test::Test(const Test& src) but doesn't call it, rather it gets     a shallow copy of GetTest() */
  std::cout << test.state << std::endl;
  Test test2(test); // Calls Test::Test(const Test& src)
  std::cout << test2.state << std::endl;

  return 0;
}

When I compiled the previous code with g++, it produced the following output:

Compiler-Generated Copy Constructor Called!
My Copy Constructor Called!

But when I compiled it with this online compiler: codepad.org
it produced the following output:

My Copy Constructor Called!
My Copy Constructor Called!

What is the constructor that is called when I initialize the object with a temporary object in case the program is compiled by g++?

The compiler-generated copy constructor isn't a special one - it's just a simple one that copies the member variable values from one object to another. When you declare a copy constructor like the one that you have, the compiler doesn't need to make a copy constructor, so it doesn't, you effectively over-write the compiler-generated one. The one that you have defined is the one and only copy constructor - for rvalues or not. For the C++11 move constructor, you have to define another type of constructor with the signature:

Test( Test&& src );

This constructor will then be used where possible to avoid creating copies when rvalues are involved.

See this page for more discussion on move constructors and rvalues

Hope that helps :)

I hope you liked my tutorial, except for that one confusing part!

Isn't the compiler-generated copy constructor called if I used an rvalue like int_vector(int_vector(5))

No. The compiler-generate move constructor will be called if you used an rvalue like int_vector(int_vector(5)). In C++03, it would be the copy-constructor (and that explains the output from codepad) that is called, but not in C++11.

and it gets a shallow copy of the temporary object just like the move constructor?

Move-semantics is not the same as a shallow copy. A move-constructor will move the resource ownership from the source to the destination (constructed object). A shallow-copy doesn't move the resource ownership, it duplicates it, which is wrong. So, the shallow copy is only the first step in a move-constructor, the second, and crucial, step is to nullify the state of the source object. You need both steps. The default move-constructor will do the following:

MyClass(MyClass&& rhs) : member1(std::move(rhs.member1)), member2(std::move(rhs.member2), ... { };

In other words, it moves all the data members from the source object to the destination, and, of course, if a data member is of a primitive type (like int, float, or any raw pointer type), it will simply be copied. That is why you need to define your own move-constructor if you hold a resource like dynamically allocated memory, which is the point of my tutorial and the "Big Five".

I also thought that initializing the object with

int_vector(<temporary object>)

would call

int_vector::int_vector(const int_vector& src)

but it doesn't. It makes a shallow copy of the temporary object instead.

By default, initialization will call the compiler-generated move-constructor, as in:

int_vector::int_vector(int_vector&& src)

So, in your example that you tested on g++ and codepad, if you make this change to the program, it should work:

class Test
{
public:
  std::string state;
  Test()
  {
    std::cout << "Default Constructor Called!" << std::endl;
  }
  Test(std::string st) : state(st) { };
  Test(const Test& src) : state("My Copy Constructor Called!") { };
#ifdef __GXX_EXPERIMENTAL_CXX0X__
  Test(Test&& src) = delete;
#endif
};

Now, both programs should output the same, because you are explicitely telling the compiler not to generate a move-constructor.

Thank you guys! :)
My confusion was because I didn't know that the compiler generates a move-constructor if I don't define my own, so I expected it to call my copy constructor (or the compiler-generated one if I didn't define my own) if I used an rvalue.

So to sum it up:

int_vector v1(5); // Calls int_vector(int)
int_vector v2(v1); // Calls int_vector(const int_vector&) - Copy Constructor
int_vector v3(std::move(v2)); // Calls int_vector(int_vector&&) - Move Constructor
int_vector v4(int_vector(3)); // Calls int_vector(int_vector&&) - Move Constructor

// And in case I don't provide a copy or move constructor, the compiler-generated ones are called.
// But in this case I have to define a copy constructor because I have dynamically-allocated memory in my class.

But does the compiler-generated move-constructor nulls out the source object? If so, why do I have to provide my own move-constructor?

Oh, I have another question.
The thing that was and is driving me crazy is that the following test program doesn't call my move-constructor as it seems to me:

#include <iostream>
#include <cstring>

class Test
{
public:
  std::string state;

  Test() : state("Default Constructor Called!") { };

  Test(std::string st) : state(st) { };

  Test(const Test& src) : state("My Copy Constructor Called!") { };

  Test(Test&& src) : state("My Move Constructor Called!") { };
};

int main()
{
  Test test(Test("Compiler-Generated Constructor Called!"));
  std::cout << test.state << std::endl;

  return 0;
}

It produces the following output:

Compiler-Generated Constructor Called!

Why?
This is the reason of my confusion, which led me to write the test programs in the previous posts in this thread to try to know what's going on.

But does the compiler-generated move-constructor nulls out the source object?

No. Because what does it mean to "null out" the source object? There is no clear and general answer to this, it is a matter of context. For some objects, it is useless to "nullify" it (as in, set it to zero). For some objects, it would be erroneous to do so. And for other objects, it would correct.

For the compiler-generated move-constructor, the rule is pretty simple. If you have a class like this:

class Foo {
  public:
    Bar1 a;
    Bar2 b;
    //.. some member functions and stuff..
};

Then, the compiler-generated move-constructor will be exactly equivalent to this:

  Foo(Foo&& rhs) : a(std::move(rhs.a)), b(std::move(rhs.b)) { };

It's that simple. It will just use the move-constructors of all the data members of the class. This solves the problem of having to know how to move an object (nullify the source or not). The assumption here is that if you don't provide a move-constructor (or explicitely delete it), then the compiler can assume that it is correct to simply move all the individual data members (with their appropriate move-constructors). This is exactly the same assumption as for the compiler-generated copy-constructor, if you don't provide one, the compiler assumes that it is OK to simply copy the data members individually. In the case of a resource-holding class, like the int_vector in my tutorial, this assumption does not work, i.e., it is not safe to simply copy the data members individually, nor is it safe to move the data members individually, and that is why a custom copy-constructor and move-constructor have to be defined.

So, to answer your question about whether the compiler's move-constructor nulls out the source object. Well, that depends entirely on the individual move-constructors of the data members (or sub-objects, to be more general). For primitive data types like int, float, or any type of pointer, the move-constructor is just a copy-constructor (i.e., shallow-copy), for the simple reason that you cannot "empty" a primitive type, and setting its memory to zero is just a waste of computational time because the assumption is that the source object (after the move) is garbage, and it is irrelevant what "flavor" of garbage it has (zero or its old value). However, for any resource-holding classes, like int_vector, std::string, or std::vector, the source object (which is destined to be thrown away) cannot be left in a state in which it thinks that it still owns the resource that it originally had because it will try to delete that resource as it is being thrown away, and for that reason, you need to put that source object into a zombie-state (i.e., living-dead object that no longer holds any resource that it thinks it should delete). So, the point of my tutorial was to say that if you do create such a resource-holding class, you must provide the copy- and move-constructor in order to do this special deep-copy and deep-move (which is a shallow-copy and zombiefication of the source object). The nice thing about all this is that once your class has a proper copy-constructor and move-constructor (and the other Big Fives as shown in the tutorial), it can become a data member of another class for which the compiler-generated copy- and move-constructors are perfectly fine (because they rely on the copy-/move-constructors of the data members).

If so, why do I have to provide my own move-constructor?

Ideally, all your resource-holding classes should be implemented as simple RAII wrappers for the given resource (file, memory, network connection, external object, etc.). When implementing those RAII classes, you'll have to implement the Big Five, as explained in my tutorial. But once you have that (a RAII class for each resources that you need), you never need to interact with raw resources anymore, you only interact with RAII classes. At this point, all your "higher-level" classes won't require any of the Big Fives, because all the underlying data members are RAII objects, which means that the compiler-generated copy-constructor, move-constructor, destructor, copy-assignment, move-assignment, and swap function are all correct. This is what "value-semantics" and "automatic behavior" means, all RAII classes can essentially be treated with the same ease as primitive value-types.

It produces the following output:

Compiler-Generated Constructor Called!

Why?

OK, now you step into another issue. Generally, in C++, there is a lot of freedom given to the compiler in terms of eliminating temporary objects when it can clearly be done. This is why you have to be somewhat careful when implementing constructors in particular. Basically, if the compiler can figure out a way to skip some constructors (because they construct temporaries), then it is allowed to do so, for efficiency.

In your example, the code says to the compiler: "construct a temporary Test from a string, then move that temporary Test into the test object by construction". In this case, the compiler is simply smart enough to figure out that it is more efficient to do: "construct the test object from a string".

These kinds of short-cuts in the chain of constructors is one of the rare optimizations in C++ that the compiler is allowed to carry out even if it means that it changes the expected behavior of the code (at least, the literal interpretation of what should happen). There are different forms and names given to this optimization, mostly referred to as "Return-Value-Optimization" (RVO) or simply "Elision of Temporaries". This is an important optimization since a literal interpretation of the code can often lead to many useless constructor calls and chains of temporary objects in-between, most compilers try to pretty aggressively by-pass this to increase performance.

So, the lesson to take out of this is that you should never expect a particular and literal sequence of constructor calls, because the compiler can choose to by-pass those that it judges to be superfluous. This means that you can't go too crazy about the kinds of things that your constructors do, you must keep it simple, just initialize the object and make sure that the semantics of the constructors are consistent amongst each other and according to the semantics that the compiler expects (this way, the optimizations by the compiler won't have any behavioral effect, except for better performance).

commented: Very helpful explanation! +0

Thank you for your replies! :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.