why isn't assignment operator called here? - c++

Edit: sorry I used "assignment constructor" instead of "assignment operator" in my original post. Fixed now.
It turns out that the copy constructor is called instead of the assignment operator in the following code. Anyone can tell me the reason behind this? Thank you.
class A
{
int i;
public:
A(int ii) { i = ii; }
A(const A& a) { i = a.i; i++; }
A& operator=(const A& a) { i = a.i; i--; }
};
int main(void)
{
A a(4);
A b = a;
return 0;
}

A a(4);
A b = a;
None of them is assignment1. Both are initialization.
First one is called direct-initialization, and second one is called copy-initialization.
The difference between them is that first one would work even if the copy-constructor is inaccessible (i.e its either private or protected), and second one would NOT work if the copy-constructor is inaccessible.
Even though the second one requires the copy-constructor to be accessible, that doesn't mean that the copy-constructor will necessarily be called. The compiler is allowed to optimize this, and so can elide the call to copy-constructor altogether. An accessible copy-constructor is needed for semantic validation.
See these topics :
Is there a difference in C++ between copy initialization and direct initialization?
When should you use direct initialization and when copy initialization?
c++ copy initialization & direct initialization, the weird case
1. And there is nothing such a thing called "assignment constructor".

operator= is not an "assignment constructor", it is an "assignment operator".
When you initialize a variable in its definition (as in A b = a), it is by definition equivalent to calling the copy constructor. ie, A b(a); and A b = a; are exactly equivalent.

Related

What happens behind the scenes when manually calling a constructor in C++?

This is an educational question, I am interested in what happens behind the scenes when I do:
SomeClass x(arg1, arg2, arg3); // An instance of SomeClass is constructed.
x = SomeClass(arg4, arg5, arg6); // Intent is to create a new instance.
SomeClass does not have operator= implemented.
Does the space allocated to x simply get overwritten as if it was newly allocated memory or what exactly happens? And when is it a good idea?
This can best be explained with the help of a small example:
Live on Coliru
struct A {
A(int a) { cout << "A::ctor\n"; } //ctor
A(const A& a) { cout << "A::copy\n"; } //copy ctor
A& operator=(const A& a) { cout << "A::operator=\n"; } //copy assign
};
int main()
{
A a(2); //calls constructor
a = A(10); //calls constructor first, then copy assignment
}
Output:
A::ctor
A::ctor
A::operator
The above is pretty self explanatory. For the first, only the constructor gets called. For the second, first the constructor is called and then copy assignment.
SomeClass does not have operator= implemented.
That doesn't matter because the compiler can generate one for you. If you explicitly delete it, then the above code will not compile. However, if you have a move constructor defined then that will be used:
(I highly recommend you read The rule of three/five/zero and understand it. It is among the top 5 things in C++ that you should know.)
A& operator=(const A& a) = delete; //copy assign deleted
A& operator=(A&& other) { cout << "move assigned\n"; } //move assign available
Now you maybe wondering what will happen if both copy and move assign are available. Lets see:
A a(2); //ctor
a = A(10); //ctor + move assign
A b(3); //ctor
b = a; // copy assign only
a = std::move(b); // move assign
For a = A(10) move assign is invoked because A(10) is an rvalue of the same type as what is on the left hand side of the =.
For the last case a = std::move(b);, we explicitly cast b to an rvalue (yes that's what std::move() does). Since it's an rvalue now, move assignment is invoked.
Does the space allocated to x simply get overwritten as if it was newly allocated memory or what exactly happens?
First the temporary is created: A(10). Space will of course be allocated for it.
It's result is then assigned to a, so previous values in a get overwritten
destructor for the temporary will be called
And when is it a good idea?
It is a good idea when you need it, it depends on your usecase. Generally I would recommend that don't copy assign unnecessarily.
Second line is call of constructor followed by call to assignment operator. Assigment default to shallow copy of non-static members into existing storage.
If you defined something that prevented compiler to create default operator=, i.e. you defined move constructor or move assignment, no assignment is possible unless you declared your own (why it is so surprising?) If default shallow copy is fine, you can write following declaration:
SomeClass& operator(const SomeClass&) = default;
= default provides mechanism to declare "default" behavior of special functions.
Now there is move assignment and in such case one would be preferred if it declared in given context. But it won't be declared by compiler if user provided destructor or copy\move constructor\assignment operator.
SomeClass& operator(SomeClass&&) = default;
Difference between two assignments exists only for class-types where "move" semantics may include transfer of ownership. For trivial types and primitive types it's a simple copy.
Compiler allowed to elide some actions including creation of storage for temporary object, so resulting code may actually write new values directly into x storage, provided that such elision won't change program behavior.

Has the compiler defined assign operator to call the copy constructor?

The rule-of-three is well known and says that if one has to define a destructor, so one has most probably to define a copy constructor and an assignment operator.
However, recently in some code, I stumbled upon a "rule-of-two": Only the destructor and the copy constructor were defined and the assign operator was left for compiler to define. My first thought was "this must be an error", but now I'm not that sure, because all compilers (gcc, msvs, intel) produced assignment operator which called the copy constructor.
Simplified, the class looks as follows:
struct A{
size_t size;
int *p;
A(size_t s): size(s), p(new int[size]){}
A(const A&a): size(a.size), p(new int[size]){
std::copy(a.p, a.p+a.size, p);
std::cout<<"copy constructor called\n";
}
~A(){
delete[] p;
}
};
And used like this:
int main(){
A a(2);
a.p[0]=42.0;
A b=a;
std::cout<<"first: "<<b.p[0]<<"\n";
}
produces the following output:
copy constructor called
first: 42
My question: It is guarantied, that the compiler defined assignment operator will call the copy constructor, or it is only a lucky coincidence, that all compilers do it that way?
Edit: It's true, I confused initialization and assignment! Replacing A b=a; by A b(0); b=aleads as expected to a double-free-error.
It's you who misunderstands what's happening, there is no assignment at all going on, only construction and initialization.
When you do
A b = a;
there's no assignment, only initialization. More precisely copy initialization. It's the same as writing
A b(a);
Initialization create a new object:
A a; // initialization
A b(a); // initialization
A c = a; // initialization
Assignment modifies an existing object:
A a;
a = 3; // assignment
In the code example, as various comments have said, there is no assignment being done; all of the examples are initializations, so the compiler-generated assignment operator is not used.

Automatically generated move constructor with not movable members

I got in a situation which is quite interesting as the code I'm working on compiles even though I'm surprised it does so I would like to ask you for your take.
The situation is this. I have a class with deleted move and copy constructors, which has user-defined assignment operators:
struct A {
A() { }
A(const A&) = delete;
A(A&& ) = delete;
A& operator=(const A& ) { return *this; }
A& operator=(A&& ) { return *this; }
};
And I have another class with A as the only member. In this class I defined the copy constructor but I kept the move constructor as default and defined the assignment operator through a call to the swap function:
class B{
public:
A a;
B()
: a{}
{ }
B(const B&)
: a{}
{ }
B(B&& other) = default;
};
int main() {
B b1;
B b2(std::move(b1)); // compiles??
}
Why does the default move constructor work, considering that it cannot simply call the move or copy constructor A? I am using gcc 4.8.4.
My original answer was wrong, so I'm starting over.
In [class.copy], we have:
A defaulted copy/
move constructor for a class X is defined as deleted (8.4.3) if X has:
— [...]
— a potentially constructed subobject 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,
— [...]
That bullet point applies to B(B&& other) = default;, so that move constructor is defined as deleted. This would seem to break compilation with std::move(), but we also have (via resolution of defect 1402):
A defaulted move constructor that is defined as deleted is ignored by overload resolution (13.3, 13.4). [ Note:
A deleted move constructor would otherwise interfere with initialization from an rvalue which can use the
copy constructor instead. —end note ]
Ignoring is the key. Thus, when we do:
B b1;
B b2(std::move(b1));
Even though the move constructor for B is deleted, this code is well-formed because the move constructor simply doesn't participate in overload resolution and the copy constructor is called instead. Thus, B is MoveConstructible - even if you cannot construct it via its move constructor.

C++ copy constructor compilation error [duplicate]

This question already has answers here:
Why copy constructor not invoked here
(2 answers)
Closed 8 years ago.
I have read some articles concerning this topic but still have problems to compile my own code.
I have class A:
class A
{
public:
List<int> data;
A(){}
A(A&){}
A& operator= (const A& a)
{
// copy the data from a to data
}
};
Class B will call class A:
class B
{
public:
A makeA()
{
A a;
return a;
}
A getA()
{
A a = makeA();
return a;
}
};
When I compile my code with g++ under Linux, I got:
no matching function for call to 'A::A(A)'.
It seems that the compiler has simply ignored the assignment operation. Can you help me out of this?
In order for this to compile, your copy constructor must take its parameter by const reference:
A(const A&){}
Adding const to your constructor signature fixes this problem (demo on ideone).
Since you are defining an assignment operator and a copy constructor, you should strongly consider adding a desctructor ~A() (see the Rule of Three).
The assignment operator is not used here.
A a = makeA();
This line is an initialization; it uses the copy constructor to copy the value returned by makeA into a. The compiler is complaining because A::A(A&) can't be used with a temporary; change it to the much more common form A(const A&) and things will be much better.
#Peter is right. The copy constructor A(A&){} wants to be A(const A&){}, instead. The reason is that A(A&){} tells the compiler to prepare to modify the A passed to the copy constructor, which does not really make sense, and certainly does not make sense in case the A you pass is a temporary.

When Does Move Constructor get called?

I'm confused about when a move constructor gets called vs a copy constructor.
I've read the following sources:
Move constructor is not getting called in C++0x
Move semantics and rvalue references in C++11
msdn
All of these sources are either overcomplicated(I just want a simple example) or only show how to write a move constructor, but not how to call it. Ive written a simple problem to be more specific:
const class noConstruct{}NoConstruct;
class a
{
private:
int *Array;
public:
a();
a(noConstruct);
a(const a&);
a& operator=(const a&);
a(a&&);
a& operator=(a&&);
~a();
};
a::a()
{
Array=new int[5]{1,2,3,4,5};
}
a::a(noConstruct Parameter)
{
Array=nullptr;
}
a::a(const a& Old): Array(Old.Array)
{
}
a& a::operator=(const a&Old)
{
delete[] Array;
Array=new int[5];
for (int i=0;i!=5;i++)
{
Array[i]=Old.Array[i];
}
return *this;
}
a::a(a&&Old)
{
Array=Old.Array;
Old.Array=nullptr;
}
a& a::operator=(a&&Old)
{
Array=Old.Array;
Old.Array=nullptr;
return *this;
}
a::~a()
{
delete[] Array;
}
int main()
{
a A(NoConstruct),B(NoConstruct),C;
A=C;
B=C;
}
currently A,B,and C all have different pointer values. I would like A to have a new pointer, B to have C's old pointer, and C to have a null pointer.
somewhat off topic, but If one could suggest a documentation where i could learn about these new features in detail i would be grateful and would probably not need to ask many more questions.
A move constructor is called:
when an object initializer is std::move(something)
when an object initializer is std::forward<T>(something) and T is not an lvalue reference type (useful in template programming for "perfect forwarding")
when an object initializer is a temporary and the compiler doesn't eliminate the copy/move entirely
when returning a function-local class object by value and the compiler doesn't eliminate the copy/move entirely
when throwing a function-local class object and the compiler doesn't eliminate the copy/move entirely
This is not a complete list. Note that an "object initializer" can be a function argument, if the parameter has a class type (not reference).
a RetByValue() {
a obj;
return obj; // Might call move ctor, or no ctor.
}
void TakeByValue(a);
int main() {
a a1;
a a2 = a1; // copy ctor
a a3 = std::move(a1); // move ctor
TakeByValue(std::move(a2)); // Might call move ctor, or no ctor.
a a4 = RetByValue(); // Might call move ctor, or no ctor.
a1 = RetByValue(); // Calls move assignment, a::operator=(a&&)
}
First of all, your copy constructor is broken. Both the copied from and copied to objects will point to the same Array and will both try to delete[] it when they go out of scope, resulting in undefined behavior. To fix it, make a copy of the array.
a::a(const a& Old): Array(new int[5])
{
for( size_t i = 0; i < 5; ++i ) {
Array[i] = Old.Array[i];
}
}
Now, move assignment is not being performed as you want it to be, because both assignment statements are assigning from lvalues, instead of using rvalues. For moves to be performed, you must be moving from an rvalue, or it must be a context where an lvalue can be considered to be an rvalue (such as the return statement of a function).
To get the desired effect use std::move to create an rvalue reference.
A=C; // A will now contain a copy of C
B=std::move(C); // Calls the move assignment operator
Remember that copy elision could occur. If you disable it by passing the -fno-elide-constructors flag to the compiler your constructor might get executed.
You can read about it here: https://www.geeksforgeeks.org/copy-elision-in-c/
Answers above do not give a 'natural' example when a move constructor is called. I found this way to call move constructor without std::move (and without suppressing copy elision by -fno-elide-constructors):
a foo(a a0) {
return a0; // move ctor is called
}
a a1 = foo(a());