Copy constructors and Assignment Operators - c++

I wrote the following program to test when the copy constructor is called and when the assignment operator is called:
#include
class Test
{
public:
Test() :
iItem (0)
{
std::cout << "This is the default ctor" << std::endl;
}
Test (const Test& t) :
iItem (t.iItem)
{
std::cout << "This is the copy ctor" << std::endl;
}
~Test()
{
std::cout << "This is the dtor" << std::endl;
}
const Test& operator=(const Test& t)
{
iItem = t.iItem;
std::cout << "This is the assignment operator" << std::endl;
return *this;
}
private:
int iItem;
};
int main()
{
{
Test t1;
Test t2 = t1;
}
{
Test t1;
Test t2 (t1);
}
{
Test t1;
Test t2;
t2 = t1;
}
}
This results in the following output (just added empy lines to make it more understandable):
doronw#DW01:~$ ./test
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the default ctor
This is the assignment operator
This is the dtor
This is the dtor
The second and third set behave as expected, but in the first set the copy constructor is called even though the assignment operator is used.
Is this behaviour part of the C++ standard or just a clever compiler optimization (I am using gcc 4.4.1)

No assignment operator is used in the first test-case. It just uses the initialization form called "copy initialization". Copy initialization does not consider explicit constructors when initializing the object.
struct A {
A();
// explicit copy constructor
explicit A(A const&);
// explicit constructor
explicit A(int);
// non-explicit "converting" constructor
A(char const*c);
};
A a;
A b = a; // fail
A b1(a); // succeeds, "direct initialization"
A c = 1; // fail, no converting constructor found
A d(1); // succeeds
A e = "hello"; // succeeds, converting constructor used
Copy initialization is used in those cases that correspond to implicit conversions, where one does not explicitly kick off a conversion, as in function argument passing, and returning from a function.

C++ standard 8.5/12
The initialization that occurs in
argument passing, function return,
throwing an exception (15.1), handling
an exception (15.3), and
brace-enclosed initializer lists
(8.5.1) is called copy-initialization
and is equivalent to the form
T x = a;
The initialization that occurs in new
expressions (5.3.4), static_cast
expressions (5.2.9), functional
notation type conversions (5.2.3), and
base and member initializers (12.6.2)
is called direct-initialization and is
equivalent to the form
T x(a);

Your first set is according to the C++ standard, and not due to some optimization.
Section 12.8 ([class.copy]) of the C++ standard gives a similar example:
class X {
// ...
public:
X(int);
X(const X&, int = 1);
};
X a(1); // calls X(int);
X b(a, 0); // calls X(const X&, int);
X c = b; // calls X(const X&, int);
The last line would be the one matching your case.

Related

Why converting constructor needs copy constructor to be declared when explicit cast performed?

From what I learned, I thought Foo a = 1 is equivalent to Foo a = (Foo)1.
With copy constructor declared, yes, they both result in calling Converting constructor.
class Foo
{
public:
// Converting constructor
Foo(int n) : value_(n) { std::cout << "Converting constructor." << std::endl; }
// Copy constructor
Foo(const Foo&) { std::couut << "Copy constructor." << std::endl; }
private:
int value_;
};
int main()
{
Foo a = 1; // OK - prints only "Converting constructor."
Foo b = (Foo)1; // OK - prints only "Converting constructor."
}
In contrast, without copy constructor, it doesn't compile even though it doesn't ever call copy constructor.
class Foo
{
public:
// Converting constructor
Foo(int n) : value_(n) { std::cout << "Converting constructor." << std::endl; }
// Copy constructor deleted
Foo(const Foo&) = delete;
private:
int value_;
};
int main()
{
Foo a = 1; // OK - prints only "Converting constructor."
Foo b = (Foo)1; // Error C2280: 'Foo::Foo(const Foo &)': attempting to reference a deleted function
}
What makes difference?
Thank you.
Foo a = 1; calls Foo(int n). (Foo)1 also calls Foo(int n). Foo b = (Foo)1; calls copy constructor Foo(const Foo&). Until C++17 the last call can be elided but the copy constructor must be available. Since C++17 copy elision is mandatory and the copy constructor isn't necessary: https://en.cppreference.com/w/cpp/language/copy_elision
Probably you're using a C++ standard before C++17 and the copy constructor is required but not called.
"From what I learned, I thought Foo a = 1 is equivalent to Foo a = (Foo)1." That's not equivalent. The first is one constructor call and the second is two constructor calls but one call can/must be elided.

Rule of 5 - Compiles without assignment operator [duplicate]

I wrote the following program to test when the copy constructor is called and when the assignment operator is called:
#include
class Test
{
public:
Test() :
iItem (0)
{
std::cout << "This is the default ctor" << std::endl;
}
Test (const Test& t) :
iItem (t.iItem)
{
std::cout << "This is the copy ctor" << std::endl;
}
~Test()
{
std::cout << "This is the dtor" << std::endl;
}
const Test& operator=(const Test& t)
{
iItem = t.iItem;
std::cout << "This is the assignment operator" << std::endl;
return *this;
}
private:
int iItem;
};
int main()
{
{
Test t1;
Test t2 = t1;
}
{
Test t1;
Test t2 (t1);
}
{
Test t1;
Test t2;
t2 = t1;
}
}
This results in the following output (just added empy lines to make it more understandable):
doronw#DW01:~$ ./test
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the default ctor
This is the assignment operator
This is the dtor
This is the dtor
The second and third set behave as expected, but in the first set the copy constructor is called even though the assignment operator is used.
Is this behaviour part of the C++ standard or just a clever compiler optimization (I am using gcc 4.4.1)
No assignment operator is used in the first test-case. It just uses the initialization form called "copy initialization". Copy initialization does not consider explicit constructors when initializing the object.
struct A {
A();
// explicit copy constructor
explicit A(A const&);
// explicit constructor
explicit A(int);
// non-explicit "converting" constructor
A(char const*c);
};
A a;
A b = a; // fail
A b1(a); // succeeds, "direct initialization"
A c = 1; // fail, no converting constructor found
A d(1); // succeeds
A e = "hello"; // succeeds, converting constructor used
Copy initialization is used in those cases that correspond to implicit conversions, where one does not explicitly kick off a conversion, as in function argument passing, and returning from a function.
C++ standard 8.5/12
The initialization that occurs in
argument passing, function return,
throwing an exception (15.1), handling
an exception (15.3), and
brace-enclosed initializer lists
(8.5.1) is called copy-initialization
and is equivalent to the form
T x = a;
The initialization that occurs in new
expressions (5.3.4), static_cast
expressions (5.2.9), functional
notation type conversions (5.2.3), and
base and member initializers (12.6.2)
is called direct-initialization and is
equivalent to the form
T x(a);
Your first set is according to the C++ standard, and not due to some optimization.
Section 12.8 ([class.copy]) of the C++ standard gives a similar example:
class X {
// ...
public:
X(int);
X(const X&, int = 1);
};
X a(1); // calls X(int);
X b(a, 0); // calls X(const X&, int);
X c = b; // calls X(const X&, int);
The last line would be the one matching your case.

removing the const in copy constructor

This is what I did originally.
class A
{ public:
A() { std::cout << "\ndefault constructor"; }
A(const A&) { std::cout << "\ncopy constructor"; }
A(int) { std::cout << "\nconversion constructor"; }
};
A a0; // print default constructor
A a1(a0); // print copy constructor note : direct initialization
A a2 = a0; // print copy constructor note : copy initialization
A a3(123); // print conversion constructor note : direct initialization
A a4 = 123; // print conversion constructor note : copy initialization (create a temp object from int)
However, if class A is slightly modified as the following (remove const in copy constructor), why are there compile error for the final line? thank you
class A
{ public:
A() { std::cout << "\ndefault constructor"; }
A(A&) { std::cout << "\ncopy constructor"; }
A(int) { std::cout << "\nconversion constructor"; }
};
A a0; // print default constructor
A a1(a0); // print copy constructor note : direct initialization
A a2 = a0; // print copy constructor note : copy initialization
A a3(123); // print conversion constructor note : direct initialization
//A a4 = 123; // compile error
A a4 = 123;
is equivalent to
A a4 = A(123); // The RHS is a temporary A object.
That works for the first case since there is a constructor that takes a A const& as the argument type.
That does not work if the argument type is A&. A temporary object can be used when the argument type is A const&, not when it is A&.
For the case A a4 = 123;, when object “a4” is being constructed, the statement
A a4 = 123;
is broken down by the compiler as
A a4 = A(123);
In above statement, one argument constructor i.e. A(int) is used to convert integer value “123” to a temporary object & that temporary object is copied to the object “a4” using copy constructor. C++ does not allow to pass temporary objects by non-const reference because temporary objects are rvalue that can't be bound to reference to non-const.
So, generically, If you're not passing your argument with a const qualifier, then you can't create copies of const objects.
One more similar example for better understanding:
class Test
{
/* Class data members */
public:
Test(Test &t) { /* Copy data members from t*/}
Test() { /* Initialize data members */ }
};
Test fun()
{
cout << "fun() Called\n";
Test t;
return t;
}
int main()
{
Test t1;
Test t2 = fun(); //compilation error with non-const copy constructor
return 0;
}
$g++ -o main *.cpp
main.cpp: In function ‘int main()’:
main.cpp:22:18: error: cannot bind non-const lvalue reference of type ‘Test&’ to an rvalue of type ‘Test’
Test t2 = fun();
~~~^~
main.cpp:8:4: note: initializing argument 1 of ‘Test::Test(Test&)’
Test(Test &t) { /* Copy data members from t*/}
^~~~

Constructor being called multiple times

I wrote the following c++ code trying to understand copy elision in c++.
#include <iostream>
using namespace std;
class B
{
public:
B(int x ) //default constructor
{
cout << "Constructor called" << endl;
}
B(const B &b) //copy constructor
{
cout << "Copy constructor called" << endl;
}
};
int main()
{
B ob =5;
ob=6;
ob=7;
return 0;
}
This produces the following output:
Constructor called
Constructor called
Constructor called
I fail to understand why is the constructor being called thrice with each assignment to object ob.
B ob =5;
This uses the given constructor.
ob=6;
This uses the given constructor because there is not a B& operator=(int) function and 6 must be converted to type B. One path to do this is to temporarily construct a B and use it in the assignment.
ob=7;
Same answer as above.
I fail to understand why is the constructor being called thrice with each assignment
As I stated above you do not have a B& operator=(int) function but the compiler is happy to provide a copy assignment operator (i.e., B& operator=(const B&);) for you automatically. The compiler generated assignment operator is being called and it takes a B type and all int types can be converted to a B type (via the constructor you provided).
Note: You can disable the implicit conversion by using explicit (i.e., explicit B(int x);) and I would recommend the use of explicit except when implicit conversions are desired.
Example
#include <iostream>
class B
{
public:
B(int x) { std::cout << "B ctor\n"; }
B(const B& b) { std::cout << B copy ctor\n"; }
};
B createB()
{
B b = 5;
return b;
}
int main()
{
B b = createB();
return 0;
}
Example Output
Note: Compiled using Visual Studio 2013 (Release)
B ctor
This shows the copy constructor was elided (i.e., the B instance in the createB function is triggered but no other constructors).
Each time you assign an instance of the variable ob of type B an integer value, you are basically constructing a new instance of B thus calling the constructor. Think about it, how else would the compiler know how to create an instance of B if not through the constructor taking an int as parameter?
If you overloaded the assignment operator for your class B taking an int, it would be called:
B& operator=(int rhs)
{
cout << "Assignment operator" << endl;
}
This would result in the first line: B ob = 5; to use the constructor, while the two following would use the assignment operator, see for yourself:
Constructor called
Assignment operator
Assignment operator
http://ideone.com/fAjoA4
If you do not want your constructor taking an int to be called upon assignment, you can declare it explicit like this:
explicit B(int x)
{
cout << "Constructor called" << endl;
}
This would cause a compiler error with your code, since it would no longer be allowed to implicitly construct an instance of B from an integer, instead it would have to be done explicitly, like this:
B ob(5);
On a side note, your constructor taking an int as parameter, is not a default constructor, a default constructor is a constructor which can be called with no arguments.
You are not taking the assignment operator into account. Since you have not defined your own operator=() implementation, the compiler generates a default operator=(const B&) implementation instead.
Thus, your code is effectively doing the following logic:
#include <iostream>
using namespace std;
class B
{
public:
B(int x) //custom constructor
{
cout << "Constructor called" << endl;
}
B(const B &b) //copy constructor
{
cout << "Copy constructor called" << endl;
}
B& operator=(const B &b) //default assignment operator
{
return *this;
}
};
int main()
{
B ob(5);
ob.operator=(B(6));
ob.operator=(B(7));
return 0;
}
The compiler-generated operator=() operator expects a B object as input, but you are passing an int value instead. Since B has a non-explicit constructor that accepts an int as input, the compiler is free to perform an implicit conversion from int to B using a temporary object.
That is why you are seeing your constructor invoked three times - the two assignments are creating temporary B objects.

Direct object initialization vs Initialization with convertion functions

The following program prints 42:
#include <iostream>
struct A
{
operator int(){ return 42; }
};
struct B
{
operator A(){ return A(); }
};
B b;
int a = A(b);
int main(){ std::cout << a << std::endl; } //42
DEMO
But if we try to define cope/move or both contructors it won't work.
#include <iostream>
struct A
{
A(A&&){ std::cout << "A(A&&)" << std::endl; }
A(A&){ std::cout << "A(A&)" << std::endl; }
operator int(){ return 42; }
};
struct B
{
operator A(){ return A(); }
};
B b;
int a = A(b);
int main(){ std::cout << a << std::endl; } //Error
DEMO
I thought, the relevant section, describing that behavior is N4296::8.5/17.7 [dcl.init]
If the destination type is a (possibly cv-qualified) class type:
[...]
— Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion
functions are enumerated (13.3.1.5), and the best one is chosen
through overload resolution (13.3). The user-defined conversion so
selected is called to convert the initializer expression into the
object being initialized. If the conversion cannot be done or is
ambiguous, the initialization is ill-formed.
It shouldn't depends on absence/presence of the constructors. We just should have to have appropriate conversion functions so as to choose the conversion sequence.
You effectively deleted the default constructor. From the standard (12.1/4, emphasis mine):
A default constructor for a class X is a constructor of class X that can be called without an argument. If
there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared
as defaulted
IF there is no user-declared constructor. But you declared two, so there is no implicit default constructor. Thus, this:
operator A(){ return A(); }
// ^^^
can't compile. That's why the error you get is
error: no matching function for call to A::A()
The code tries to call your conversion operator - but the body isn't valid.