I know has been asked several times, I read several tutorial about it but still I can't figure out an answer.
Let's have the typical class
class Foo
{
public:
Foo() : i_(0) { std::cout << "Foo Empty ctor" << std::endl; }
Foo(const Foo& foo) : i_(foo.i_) { std::cout << "Foo Copy ctor" << std::endl; }
Foo(int i) : i_(i) { std::cout << "Foo Param ctor" << std::endl; }
Foo(Foo&& foo) : i_(foo.i_) { foo.i_ = 0; std::cout << "Foo Move ctor" << std::endl; }
Foo(const Foo&& foo) : i_(foo.i_) { std::cout << "Foo const Move ctor" << std::endl; }
~Foo() { std::cout << "Foo " << i_ << " destroyed\n"; }
Foo& operator=(Foo&& other)
{
i_ = std::move(other.i_);
std::cout << "Foo Move assigned\n";
return *this;
}
private:
int i_;
};
Foo getFoo()
{
return Foo(99);
}
int main()
{
Foo f1;
Foo f2(5);
Foo f3(f2);
Foo f4(Foo(4));
Foo f5(std::move(Foo(6));
Foo f6 = getFoo();
Foo f7 = Foo(1);
}
Output
// f1
Foo Empty ctor
// f2
Foo Param ctor
// f3
Foo Copy ctor
// f4 - copy elision
Foo Param ctor
// f5
Foo Param ctor
Foo Move ctor
// f6 - copy elision
Foo Param ctor
// f7 - copy elision
Foo Param ctor
So, except when I call explicitly the move operator, the copy elision optimization do the same work even if do not declare move semantics.
1 - In case when we are not dealing with pointers and I'm not going to call an explicit std::move (so I suppose an implicit call like in the examples) does it have sense declaring move semantics if the copy elision do the same without is?
2 - Does have generally sense declaring move semantics for small classes with simple data? Does it have real advantages?
Related
I have two similar pieces of code. The first version unexpectedly calls the default constructor while the second doesn't. They both call the move operator / move constructor, respectively, as expected.
class MyResource
{
public:
MyResource() : m_data(0) { std::cout << "Default Ctor" << std::endl; }
MyResource(int data) : m_data(data) { std::cout << "Int Ctor" << std::endl; }
MyResource(MyResource const& other) = delete;
MyResource& operator=(MyResource const& other) = delete;
MyResource(MyResource&& other) noexcept : m_data(other.m_data) { std::cout << "Move Ctor" << std::endl; }
MyResource& operator=(MyResource&& other) noexcept { std::cout << "Move Op" << std::endl; m_data = other.m_data; return *this; }
~MyResource() { std::cout << "Dtor" << std::endl; }
private:
int m_data = 0;
};
class MyWrapper
{
public:
MyWrapper(MyResource&& resource)
// : m_resource(std::move(resource)) // Version 2
{
// m_resource = std::move(resource); // Version 1
}
private:
MyResource m_resource;
};
My test usage is:
MyWrapper* wrapper = new MyWrapper(MyResource(1));
delete wrapper;
With Version 1, I get:
Int Ctor
Default Ctor
Move Op
Dtor
Dtor
While Version 2 outputs:
Int Ctor
Move Ctor
Dtor
Dtor
What's the reason behind this difference?
Why does version 1 call the default constructor?
Members are initialized before the construct body runs. A much simpler example to see the same:
#include <iostream>
struct foo {
foo(int) { std::cout << "ctr\n";}
foo() { std::cout << "default ctr\n";}
void operator=(const foo&) { std::cout << "assignment\n"; }
};
struct bar {
foo f;
bar(int) : f(1) {}
bar() {
f = foo();
}
};
int main() {
bar b;
std::cout << "---------\n";
bar c(1);
}
Output:
default ctr
default ctr
assignment
---------
ctr
You cannot initialize a member in the body of the constructor! If you do not provide an initializer, either in the member initializer list or as an in class initializer, then f is default constructed. In the constructor body you can only assign to an already initialized member.
Given is the following simple class:
#include <iostream>
class Foo{
int a, b;
public:
Foo(int _a = 0, int _b = 0) : a(_a), b(_b) {
std::cout << "Foo(int, int)" << "\n";
}
Foo(const Foo& foo) : a(foo.a), b(foo.b) {
std::cout << "Foo(const Foo&)" << "\n";
}
Foo& operator=(const Foo& other) {
std::cout << "operator=(const Foo&)" << "\n";
if(this != &other){
a = other.a;
b = other.b;
}
return *this;
}
~Foo() {
std::cout << "~Foo()" << "\n";
}
Foo operator+(const Foo& other){
std::cout << "foo.operator+(const Foo&)" << "\n";
return Foo(a + other.a, b + other.b);
}
};
and the main:
int main(){
Foo f1, f2(1, 2), f3;
std::cout << "-----------" << "\n";
f3 = f1 + f2; // (*)
std::cout << "-----------" << "\n";
return 0;
}
I compiled with -std=c++11 and for demonstration purposes also with the -fno-elide-constructors flag. The output reads as:
Foo(int, int)
Foo(int, int)
Foo(int, int)
-----------
foo.operator+(const Foo&) // (1)
Foo(int, int) // (2)
Foo(const Foo&) // (3)
~Foo() // (4)
operator=(const Foo&) // (5)
~Foo() // (6)
-----------
~Foo()
~Foo()
~Foo()
As far as I understand it correctly, the following happens for the line f3 = f1 + f2; (*):
Obviously, f1.operator+(f2) is called.
A new (temporary) object is created. Let's denote it by T.
Since the operator+() function does not return a reference, we call to copy constructor in order to construct a copy T2 of T. We return T2.
The destructor is called in order to delete the temporary object T.
We call the copy assignment operator to assign all members of T2 to f3.
My question: I don't really see what's the purpose of (6). Why is there an additional call to the destructor, i.e. what am I missing here?
what am I missing here?
The destruction of T2.
There are 3+2 constructors called, there must be 3+2 destructors called too!
Context: I was making experiments to learn when does gcc perform RVO, and if not, when does it use move semantics. My version of gcc is g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4).
Question: I have a function that returns a Foo by value. Compiler cannot perform RVO because there are two possible named return values. When I use the ternary operator to select which of Foo to return, then I need to explicitly call std::move to avoid the copy. I do not need the std::move when using an if statement. Why the discrepancy?
Code:
#include <iostream>
using namespace std;
struct Foo {
std::string s;
Foo() { cout << "Foo()\n"; }
~Foo() { cout << "~Foo()\n"; }
Foo(const Foo& other) : s(other.s) { cout << "Foo(const Foo&)\n"; }
Foo(Foo&& other) noexcept : s(move(other.s)) { cout << "Foo(Foo&&)\n"; }
};
Foo makeFooIf(bool which) {
Foo foo1; foo1.s = "Hello, World1!";
Foo foo2; foo2.s = "Hello, World2!";
if (which) return foo1;
else return foo2;
}
Foo makeFooTernary(bool which) {
Foo foo1; foo1.s = "Hello, World1!";
Foo foo2; foo2.s = "Hello, World2!";
return which ? foo1 : foo2;
}
Foo makeFooTernaryMove(bool which) {
Foo foo1; foo1.s = "Hello, World1!";
Foo foo2; foo2.s = "Hello, World2!";
return which ? move(foo1) : move(foo2);
}
int main()
{
cout << "----- makeFooIf -----\n";
Foo fooIf = makeFooIf(true);
cout << fooIf.s << endl;
cout << "\n----- makeFooTernary -----\n";
Foo fooTernary = makeFooTernary(true);
cout << fooTernary.s << endl;
cout << "\n----- makeFooTernaryMove -----\n";
Foo fooTernaryMove = makeFooTernaryMove(true);
cout << fooTernaryMove.s << endl;
cout << "\n----- Cleanup -----\n";
return 0;
}
Output:
----- makeFooIf -----
Foo()
Foo()
Foo(Foo&&)
~Foo()
~Foo()
Hello, World1!
----- makeFooTernary -----
Foo()
Foo()
Foo(const Foo&)
~Foo()
~Foo()
Hello, World1!
----- makeFooTernaryMove -----
Foo()
Foo()
Foo(Foo&&)
~Foo()
~Foo()
Hello, World1!
----- Cleanup -----
~Foo()
~Foo()
~Foo()
There is an implicit move in certain situations:
§12.8.32
When the criteria for elision of a copy/move operation are met, but
not for an exception-declaration, and the object to be copied is
designated by an lvalue, or when the expression in a return statement
is a (possibly parenthesized) id-expression that names an object with
automatic storage duration declared in the body or
parameter-declaration-clause of the innermost enclosing function or
lambda-expression, overload resolution to select the constructor for
the copy is first performed as if the object were designated by an
rvalue. If the first overload resolution fails or was not performed,
or if the type of the first parameter of the selected constructor is
not an rvalue reference to the object’s type (possibly cv-qualified),
overload resolution is performed again, considering the object as an
lvalue.
My bolding
I am studying constructor, copy constructors and copy assignment a while and I read a fair amount of contents mainly from here as In which situations is the C++ copy constructor called? and What is The Rule of Three? and others, but I still no deeply understanding their exactly behavior. I am performing some tests with the class Boo and Foo bellow and their results are not the ones that I was expecting. In the class Boo I explictly declared its default constructor, copy constructor and copy assingment, and I defined them to do nothing. From this I was expecting that the objects from this class would have member variables with random values, what was indeed observed. Nevertheless, this behavior was not seen with the objects from the class Foo. The only difference between these classes are that the later is a singleton, but its default constructor, copy constructor and copy assignment still explicit declared and defined to do nothing. From this I state my question: Why the values of the member variables of objects from class Foo are not randomly initialized, but always has de same value of "0" and " "?
#include <iostream>
class Boo {
private:
int x;
char y;
public:
int getInt() { return x; }
char getChar() { return y; }
Boo () { std::cout << "Boo default constructor\n"; }
Boo ( const Boo& other ) { std::cout << "Boo copy constructor\n"; }
Boo& operator= ( const Boo& other) { std::cout << "Boo copy assinment\n";
return *this;}
};
class Foo {
private:
int x;
char y;
static Foo *instance;
protected:
Foo() { std::cout << "Foo default constructor\n"; }
Foo ( const Foo& other ) { std::cout << "Foo copy constructor\n"; }
Foo& operator=( const Foo& other) { std::cout << "Foo copy assignment\n"; }
public:
static Foo & uniqueInst();
int getInt() { return x; }
char getChar() { return y; }
};
Foo *Foo::instance = 0;
Foo & Foo::uniqueInst(){
if(!instance) instance = new Foo();
return *instance;
};
int main(){
Boo b1; // default constructor
Boo b2; // default constructor
Boo b3 = b1; // copy constructor
b2 = b1;
std::cout << b1.getInt() << std::endl; // Random values since the constructor does nothing
std::cout << b1.getChar() << std::endl;
std::cout << b2.getInt() << std::endl; // Random values since the copy assignment does nothing
std::cout << b2.getChar() << std::endl;
std::cout << b3.getInt() << std::endl; // Random values since the copy constructor does nothing
std::cout << b3.getChar() << std::endl;
Foo *foo;
foo = &Foo::uniqueInst(); // defaulf construtor
std::cout << foo << std::endl;
std::cout << foo->getInt() << std::endl; // Why not random values?
std::cout << foo->getChar() << std::endl;
};
Consider:
struct Foo {
Foo () { std::cout << "default ctor" << std::endl; }
Foo (const Foo&) { std::cout << "copy ctor" << std::endl; }
Foo& operator= (const Foo&) { std::cout << "copy op" << std::endl; return *this; }
Foo (Foo&&) { std::cout << "move ctor" << std::endl; }
Foo& operator= (Foo&&) { std::cout << "move op" << std::endl; return *this; }
~Foo () { std::cout << "dtor" << std::endl; }
};
Foo process1 (Foo&& foo) {
return foo;
}
Foo process2 (Foo&& foo) {
return std::move (foo);
}
and usage:
Foo foo {};
foo = process1 (std::move (foo));
gives result:
default ctor
copy ctor
move op
dtor
dtor
and usage:
Foo foo {};
foo = process2 (std::move (foo));
gives result:
default ctor
move ctor
move op
dtor
dtor
Which one is preferred one (process1 or process2)?
Does it mean that in the first example (process1) if I pass object by rvalue reference to the function, which returns an Object, the copy will be made if I do not use std::move() ?
Compiler: GCC 5.2.1
In the first version
Foo process1 (Foo&& foo) {
return foo;
}
you pass it as an rvalue reference, but by the "if it has a name" heuristic, it is treated as an lvalue within the function, hence the copy ctor.
The second version "remakes" this an rvalue using std::move (which is exactly what it is meant for). Thus the copy ctor is avoided.
It's pretty reasonable to expect that a move will not be more expensive than a copy, so, given a real-life situation boiling down to this, you might prefer the second version.