destructor and unique_ptr - c++

I have the following code
class A {
public:
A(){}
~A(){}
private:
std::vector<std::unique_ptr<double> > x;
};
A f() {
A a;
return a;
}
int main() {
A a=f();
return 0;
}
It does not compile (gcc 4.7), unless I comment out the destructor. Actually, I don't really need this destructor in my code, I just wanted to use it for debug purpose.
However, I don't understand what is happening and I therefore fear I have done something wrong. What is happening here ?

That is because the presence of an explicitly defined destructor prevents the implicit generation of a move constructor for A.
Per Paragraph 12.8/9 of the C++11 Standard:
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 without a move constructor, to return the value from f() the compiler will try to invoke the implicitly generated copy constructor (which is still being generated for backwards compatibility). However, std::unique_ptr is non-copyable. Hence, the error.
Explicitly defining a move constructor (or declaring one as defaulted, as suggested by juanchopanza in the comments) will fix the problem.

Related

Has it been established if move/copy constructor/assignment are "deleted" or "not declared" in C++17?

I was looking at this answer regarding What are the rules for automatic generation of move operations?, and am hopeful that the answer has now been well established by now.
The slide that shows what constructors/assignment operators are "not declared", "defaulted" or "deleted", based on what has been declared in the class, shows:
That was taken from these slides, with the red squares meaning that this behaviour is deprecated.
When compiling the following:
#include <iostream>
struct X
{
template<typename...T>
X(T&&...) {
std::cout << "Yay!\n";
}
~X() {}
};
int main() {
X x0;
X x1{x0};
X x2{std::move(x0)};
}
It would appear that they have been "not declared", since it compiles and the output is "Yay!" three times (which is good, at least for me). But I want to confirm that I can rely on this behaviour.
Edit
It has been pointed out by Frank that, if a copy constructor is also added, it still says "Yay!" three times, which is interesting behaviour. Doing further testing, if a move constructor is added, it will only say "Yay!" twice. Can anyone explain this behaviour?
According to N4659 (almost C++17 standard), they are still defined as defaulted but the behavior is (still) deprecated.
For the copy constructor, see [class.copy.ctor]/6:
If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted. The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.
For the copy assignment, see [class.copy.assign]/2:
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted. The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor.

Is an empty constructor with initializer list considered trivial?

Is the following constructor considered trivial?
struct A
{
A() : a(nullptr) {}
private:
int* a;
};
These examples makes me a little confused. With c++11 this should also be possible:
struct A
{
private:
int* a{nullptr};
};
and a should be properly initialized to nullptr. Here I did not define the constructor, but it should have the same form as the first implementation. Is either of these classes considered trivial?
The purpose of me asking is if I can expect move/copy constructors and assignment operators being automatically generated.
Is the following constructor considered trivial?
A() : a(nullptr) {}
No, because it is user-defined.
struct A
{
private:
int* a{nullptr};
};
No, because it has brace initializer for a non-static member.
According to the standard (emphasis mine):
12.1 Constructors....
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 (8.40).....
A default constructor is trivial if it is not user-provided and if:....
— no non-static data member of its class has a brace-or-equal-initializer, and....
Otherwise, the default constructor is non-trivial.
The purpose of me asking is if I can expect move/copy constructors and assignment operators being automatically generated.
As #M.M and #NicolBolas commented, generation of these constructors and operators are not impacted by existence of the trivial constructor.
The rules are a bit complicated and not very consistent.
The copy constructor is generated only if there is no explicitly declared one. (And it is deleted if the move constructor or the move assignment operator is declared.)
Similarly, the copy assignment operator is generated only if there is no explicitly declared one. (And again, it is deleted if the move constructor or the move assignment operator is declared.)
The move constructor is generated only if there are no explicitly declared move constructor, move operator, copy constructor, copy assignment and destructor.
The same rule is for the move assignment operator.

What's with the copy-constructor if the class contains a user-declared destructor?

The Standard in section 12.8/7 says:
If the class definition does not explicitly declare a copy
constructor, one is declared implicitly. If the class definition
declares a move constructor or move assignment operator, the
implicitly declared copy constructor is defined as deleted; otherwise,
it is defined as defaulted (8.4). The latter case is deprecated if the
class has a user-declared copy assignment operator or a user-declared
destructor. Thus, for the class definition
struct X {
X(const X&, int);
};
a copy constructor is implicitly-declared. If the user-declared
constructor is later defined as
X::X(const X& x, int i =0) { /∗ ... ∗/ }
I can't get the point of that The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor. In the example the Standard neither provides a user-declared copy assignment operator nor a destructor. What will happen if we declare a destructor or a copy assignment operator? I've tried to do that as follows:
struct A
{
~A(){ };
};
A::A(const A&){ }; //error
int main(){ }
DEMO
but in the example we still have the implicitly-declared copy constructor. What does that rule actual mean?
I thought that if we write the following:
struct A
{
A(){ };
A(const A&&){ };
~A(){ };
};
A a;
A t = a; //error: call to implicitly-deleted copy constructor of 'A'
int main()
{
}
DEMO
the copy-constructor won't explicitly deleted. But thats's not the case.
This deprecation basically incorporates the Rule of Three (Five). If a user-declared copy assignment operator or destructor is provided, the fact that the copy constructor is defined as defaulted (and not as deleted) is deprecated. That should prevent you from depending upon such an implicitly declared copy constructor in future.
In the example the Standard provides neither copy assignment nor
destructor are user-decalred.
The example has nothing to do with the deprecation.
I've tryied to do that as follows: […] but in the example we still have the impliclty-declared copy
constructor.
You cannot define an implicitly declared copy constructor - because it's already defined, by = default (no pun intended). You would have to declare it yourself first.
I thought that If we wirte the following: […] the copy-constructor won't explicitly deleted. But it's not true.
You quoted the rule that explicitly specifies that the copy constructor will be implicitly defined as deleted if a move constructor is declared:
If the class definition declares a move constructor or move
assignment operator, the implicitly declared copy constructor is
defined as deleted;
Clearly,
A(const A&&){ }
Is a move constructor according to [class.copy]/3. If you removed this move constructor then your example compiles, although it uses said deprecated feature.
Deprecated normally means that something will work, but that it is frowned upon and may not work in the future. I think the standard is saying that if you create a user-declared copy assignment operator or a user-declared destructor it will still create a default copy constructor (if you haven't) - but it may not in the future. So they want you to create your own copy constructor now, if you have one of the other two, and in the future they may force you to.

Understanding Default Move Constructor Definition

While reading about the move constructor from the current standard, I can see the following about this:
12.8 Copying and moving class objects
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, and
— X does not have a user-declared destructor.
[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]
I think note section clearly mentions that fall-back for default move constructor would be copy constructor. To understand this concept I wrote the small program to understand this concept.
#include<iostream>
struct handleclass {
public:
handleclass():length{0}, p{nullptr} {}
handleclass(size_t l):length{l},p{new int[length]} { }
~handleclass() { delete[] p; }
size_t length;
int* p;
};
handleclass function(void) {
handleclass x(10);
return x;
}
int main() {
handleclass y;
std::cout<<y.length<<std::endl;
y = function();
std::cout<<y.length<<std::endl;
handleclass a;
handleclass b(10);
a = std::move(b);
return 0;
}
Obviously this program is incorrect and would have undefined behaviour(terminate) due to the shallow copy of resources by two objects. But my focus is to understand the default move constructor generated and used in program. I hope this example make sense.
In the above program, in both cases where move constructor should be called, it appears to me that compiler is using default copy constructor.
Based on the above rule mentioned in the standard I think we should have got the compiler error as now program explicitly trying to call the move constructor and neither user has implemented nor compiler generates default(implicitly) as above rule does not satisfy?.
However this is getting compiled without any warning/error and running successfully. Could somebody explains about default(implicitly) move constructor concepts? Or I am missing something?.
You're forgetting about copy elision which means that y = function(); may not actually invoke any copy or move constructors; just the constructor for x and the assignment operator.
Some compilers let you disable copy elision, as mentioned on that thread.
I'm not sure what you mean by "in both cases where move constructor should be called". There are actually zero cases where move constructor should be called (your object does not have a move constructor), and one case where copy constructor could be called (the return statement) but may be elided.
You have two cases of assignment operator: y = function(); and a = std::move(b); . Again, since your class does not have a move-assignment operator, these will use the copy-assignment operator.
It would probably help your testing if you added code to your object to cout from within the copy constructor and move constructor.
Indeed, there is no implicit move constructor due to the user-declared destructor.
But there is an implicit copy constructor and copy-assignment operator; for historical reasons, the destructor doesn't inhibit those, although such behaviour is deprecated since (as you point out) it usually gives invalid copy semantics.
They can be used to copy both lvalues and rvalues, and so are used for the function return (which might be elided) and assignments in your test program. If you want to prevent that, then you'll have to delete those functions.
A move constructor and move assignment operator have not been implicitly declared because you have explicitly defined a destructor. A copy constructor and copy assignment operator have been implicitly declared though (although this behaviour is deprecated).
If a move constructor and move assignment operator have not been implicitly (or explicitly) declared it will fall back to using the copy equivalents.
As you are trying to call move-assignment it will fall back to using copy assignment instead.

Will move constructor and move assignment be generated if I default the copy constructor?

I am a little confused about how best to define a copyable but not moveable class. It seems to me that deleting the move constructor is a bad idea because then I couldn't construct from a temporary. On the other hand I don't want the compiler to implicitly generate a move constructor for me that may be wrong or may lead to unexpected behavior. My question is whether the compiler is allowed to implicitly generate a move constructor for the following class:
class C {
public:
int i_;
C(const C&) = default;
}
[class.copy]/9 after CWG 1402:
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, and
X does not have a user-declared destructor.
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
[and ...]
Since the copy constructor is user-declared (even though it is defaulted, which means it is not user-provided), a move constructor will not be implicitly declared as defaulted.
You're right that if you were to explicitly delete the move constructor, you would not be able to construct from temporaries because the deleted move constructor would be chosen by overload resolution. So the approach you have used for a copyable but not moveable class is the right one.
Relative to your example the compiler will implicitly declare a move constructor because the first declaration of your copy constructor is the default definition. In this case the copy constructor is considered as implicitly defined. But if you would change your class definition the following way
class C {
public:
int i_;
C(const C&);
};
C::C( const C & ) = default;
then in this case the move constructor would not be generated by the compiler becasue the copy constructor is explicitly declared by the user.