In Stroustrup's Tour of C++ he mentions:
If you are explicit about some defaults, other default definitions will not be generated.
However, this doesn't seem to be the case with gcc 4.9.1. Take for example:
#include <iostream>
using namespace std;
struct A
{
int data;
A() = default;
A(const A&) = default;
};
int main()
{
A a;
a.data = 10;
A b(a);
A c;
c = a;
cout<<b.data<<endl;
cout<<c.data<<endl;
return 0;
}
It compiles and gives the expected output. i.e. the default assignment operator has been generated.
Did I misunderstand Stroustrup's comment?
Firstly, your code doesn't use the class's assignment operator, so it doesn't demonstrate that it exists. However, adding a use of it
c = a;
will work, so the question still stands.
It's not the case that declaring any special function prevents all other special functions from being generated, which seems to be how you're interpreting the quote. Declaring some will inhibit some others. Not having read the book to put the quote in context, I can't say whether or not it's misleading.
The rules are, roughly:
declaring any constructor will prevent an implicit default constructor;
declaring a move constructor or move-assignment operator will prevent an implicit copy constructor and copy-assignment operator;
declaring a destructor, copy constructor or copy-assignment operator will prevent an implicit move constructor and move-assignment operator;
That's a simplification; there are more nuances and exceptions. Read the language specification if you want the gory details.
Those declarations are currently generated by default (which is why the code you provided works), but this behavior is subject to be removed at any time in further revisions to the C++ standard (deprecated).
In Stroustrup's C++11 FAQ, he states
If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, no move is generated by default.
That is pretty self explanatory. However, he goes on to say that...
If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, any undeclared copy operations are generated by default, but this is deprecated, so don't rely on that.
He then lists a few examples:
class X1 {
X1& operator=(const X1&) = delete; // Disallow copying
};
This implicitly also disallows moving of X1s. Copy initialization is allowed, but deprecated.
class X2 {
X2& operator=(const X2&) = delete;
};
This implicitly also disallows moving of X2s. Copy initialization is allowed, but deprecated.
class X3 {
X3& operator=(X3&&) = delete; // Disallow moving
};
This implicitly also disallows copying of X3s.
class X4 {
~X4() = delete; // Disallow destruction
};
This implicitly also disallows moving of X4s. Copying is allowed, but deprecated.
Related
I am wondering why the committee has decided that a move constructor is implicitly deleted when a destructor is defined.
#include <iostream>
#include <vector>
#include <memory>
struct A {
~A(){};
std::unique_ptr<int> a;
};
int main()
{
A a;
A b = std::move(a);
}
http://coliru.stacked-crooked.com/a/c0c067fc51260794
Is there any utopic use-case for which this rule "of not default moving the members" makes sense?
The logic is: if you define a destructor that implies it releases resources there, so that compiler-generated constructors and assignments are probably not adequate.
The thinking is that if you find the need to declare either a destructor or copy special member for your class, then the class must contain resources that need special handling, so implicitly declaring move special members could be dangerous in that the generated code may result in incorrect behavior.
A simple example is
struct String
{
char *s = nullptr;
size_t size = 0;
String(char const* s); // makes a copy of the string
~String()
{
delete[] s;
}
};
If the standard allowed implicit move constructor generation, what would it do? It would simply initialize s and size in the target object, but it would not assign them to nullptr and 0 respectively in the source object. This then leads to double deletion in the source and target object's destructor and undefined behavior.
Implicit generation of the copy assignment operator leads to similar issues as well.
Note that C++11 deprecated implicit generation of the copy special members as well for the above example. Unfortunately, they couldn't be defined as deleted because it would break too much code.
[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.
While I was learning std::move, I found a strange issue.
If I add only a destructor that do nothing to a perfect program, I will get a compile error.
#include <iostream>
using namespace std;
class M {
public:
int database = 0;
M &operator=(M &&other) {
this->database = other.database;
other.database = 0;
return *this;
}
M(M &&other) { *this = std::move(other); }
M(M &m) = default;
M() = default;
~M() { /* free db */ }
};
class B {
public:
M shouldMove;
//~B(){} //<--- ## Adding this line will cause compile error. ##
};
int main() {
B b;
B b2 = std::move(b); //## error at this line if the above line is added
return 0;
}
Live code: https://ideone.com/UTR9ob
The error is invalid initialization of non-const reference of type 'B&' from an rvalue of type 'std::remove_reference<B&>::type {aka B}'.
Question:
(1) Which rules of C++ syntax enforce that? In other words, what does the error mean?
(2) If I want to add destructor that do almost nothing (e.g. only print debug log) to B, do I really have to follow the rule-of-five instead? If no, how to make it compile? Following the rule of five just because of that is too tedious and dirty in my opinion.
I think the rule of zero is just a good practice.
However, from this example, it seems to me that it is a hard rule that if violated, I will get compile error.
The implicitly-declared move constructor is only present if a class does not have a user-declared destructor. Therefore the answer to 2. is YES.
The answer to 1. is that this is hard rule and can be found in 12.8, paragraph 9 of the 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.
[ 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 ]
The best way of getting this to run,is by using something like a smart pointer, i.e., a base class or member that does define all five special members (and very little else) so that you don't have to. In this case, an integer handle equivalent to std::unique_pointer should work well. However, keep in mind that databases, like files, can have errors while closing, so standard non-throwing destructor semantics don't cover all cases.
Environment: VS2015 Update 3, 64 bit (debug|release) compile
In the code below, if I uncomment the Maybe(X&) = delete line, I get the warning mentioned in the code below and in the title of the question.
Now, I am aware, that there are certain rules in C++(11?), which might render
the explicit deletion of that constructor obsolete. Only, even after searching
the web for a while, I could not find a definite rule, which would confirm, that
if I only delete Maybe(const X&) = delete, the compiler will not auto generate the other copy constructor.
So my question is first and foremost:
Can anyone point me to spot in the C++ specification, which clearly defines the
rules for auto generation of copy constructors? Alternatively, some less official
easy to remember rule of thumb on how to be certain of what will happen would also be welcome.
template <class X>
class Maybe
{
X *m_just;
public:
explicit Maybe(const X& x)
: m_just(new X(x))
{}
Maybe()
: m_just(nullptr)
{}
Maybe(const Maybe<X>&& other)
: m_just(other.m_just)
{
other.m_just = nullptr;
}
Maybe(const Maybe<X>&) = delete;
// If line below is uncommented, this produces the warning:
// warning C4521: 'Maybe<Int32>': multiple copy constructors specified
// Maybe(Maybe<X>&) = delete;
~Maybe()
{
delete m_just;
m_just = nullptr;
}
// ... more members and code which are not related to question
// ...
};
Please do not comment on the whole idea of that class. It is just private tinkering in my private lab... ;)
The compiler will only generate one of Maybe(Maybe<X>&) and Maybe(const Maybe<X>&).
The conditions for the choice are listed in section 12.8, paragraph 8:
The implicitly-declared copy constructor for a class X will have the
form X::X(const X&) if
each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and
for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy constructor whose
first parameter is of type const M& or const volatile M&
Otherwise, the implicitly-declared copy constructor will have the form
X::X(X&)
Or more informally, the parameter will be const if and only if everything that needs to be copied can be "const-copied".
Deleting one will not cause the other to be generated.
Since you have no base class and your only member is a pointer, the generated constructor will be of the const variety and you can leave out the line causing an error.
My C++ is a little bit rusty so this might be wrong, but I don't think you should even have a non-const copy constructor (except for the move constructor naturally).
If a = b changes b you're going to surprise some people. Perhaps it's legitimate for the copy constructor to change the source in a way that's not externally visible, but in that case I think you're better off using const_cast inside the const copy constructor.
The error I don't think is about delete, it's about defining multiple copy constructors, which isn't allowed. I suspect even allowing the non-const copy constructor is a bit of a wart in the language.
other.m_just = nullptr;
This line should not compile. Something must be wrong with your compiler, or your code example doesn't exactly reflect what you're compiling.
Anyway, the move constructor should take a non-const rvalue reference.
The commented line is simply not necessary. Just leave it out. If you define the copy constructor (even as deleted), no other form of the copy constructor is defined by the compiler, ever, so if you leave the line out, there simply is no constructor that takes a non-const reference to Self, meaning that overload resolution will just pick the const reference copy constructor, which is deleted anyway.
What is the rationale behind the different treatment of implicitly and explicitly deleted move constructors in the C++11 standard, with respect to the implicit generation of move constructors of containing/inheriting classes?
Do C++14/C++17 change anything? (Except DR1402 in C++14)
Note: I understand what is happening, I understand that it is according to the C++11 standard's rules, I'm interested in the rationale for these rules that imply this behavior (please make sure not to simply restate that it is the way it is because the standard says so).
Assume a class ExplicitDelete with an explicitly deleted move ctor and an explicitly defaulted copy ctor. This class isn't move constructible even though a compatible copy ctor is available, because overload resolution chooses the move constructor and fails at compile time due to its deletion.
Assume a class ImplicitDelete which either contains or inherits from ExplicitDelete and does nothing else. This class will have its move ctor implicitly declared as deleted due to C++11 move ctor rules. However, this class will still be move constructible via its copy ctor. (Does this last statement have to do with resolution of DR1402?)
Then a class Implicit containing/inheriting from ImplicitDelete will have a perfectly fine implicit move constructor generated, that calls ImplicitDelete's copy ctor.
So what is the rationale behind allowing Implicit to be able to move implicitly and ImplicitDelete not to be able to move implicitly?
In practice, if Implicit and ImplicitDelete have some heavy-duty movable members (think vector<string>), I see no reason that Implicit should be vastly superior to ImplicitDelete in move performance. ImplicitDelete could still copy ExplicitDelete from its implicit move ctor—just like Implicit does with ImplicitDelete.
To me, this behavior seems inconsistent. I'd find it more consistent if either of these two things happened:
The compiler treats both the implicitly and explicitly deleted move ctors the same:
ImplicitDelete becomes not move-constructible, just like ExplicitDelete
ImplicitDelete's deleted move ctor leads to a deleted implicit move ctor in Implicit (in the same way that ExplicitDelete does that to ImplicitDelete)
Implicit becomes not move-constructible
Compilation of the std::move line utterly fails in my code sample
Or, the compiler falls back to copy ctor also for ExplicitDelete:
ExplicitDelete's copy constructor is called in all moves, just like for ImplicitDelete
ImplicitDelete gets a proper implicit move ctor
(Implicit is unchanged in this scenario)
The output of the code sample indicates that the Explicit member is always moved.
Here's the fully working example:
#include <utility>
#include <iostream>
using namespace std;
struct Explicit {
// prints whether the containing class's move or copy constructor was called
// in practice this would be the expensive vector<string>
string owner;
Explicit(string owner) : owner(owner) {};
Explicit(const Explicit& o) { cout << o.owner << " is actually copying\n"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is moving\n"; }
};
struct ExplicitDelete {
ExplicitDelete() = default;
ExplicitDelete(const ExplicitDelete&) = default;
ExplicitDelete(ExplicitDelete&&) noexcept = delete;
};
struct ImplicitDelete : ExplicitDelete {
Explicit exp{"ImplicitDelete"};
};
struct Implicit : ImplicitDelete {
Explicit exp{"Implicit"};
};
int main() {
ImplicitDelete id1;
ImplicitDelete id2(move(id1)); // expect copy call
Implicit i1;
Implicit i2(move(i1)); // expect 1x ImplicitDelete's copy and 1x Implicit's move
return 0;
}
So what is the rationale behind allowing Implicit to be able to move implicitly and ImplicitDelete not to be able to move implicitly?
The rationale would be this: the case you describe does not make sense.
See, all of this started because of ExplicitDelete. By your definition, this class has an explicitly deleted move constructor, but a defaulted copy constructor.
There are immobile types, with neither copy nor move. There are move-only types. And there are copyable types.
But a type which can be copied but has an explicitly deleted move constructor? I would say that such a class is a contradiction.
Here are the three facts, as I see it:
Explicitly deleting a move constructor is supposed to mean you can't move it.
Explicitly defaulting a copy constructor is supposed to mean you can copy it (for the purposes of this conversation, of course. I know you can still do things that make the explicit default deleted instead).
If a type can be copied, it can be moved. That's why the rule about implicitly deleted move constructors not participating in overload resolution exists. Therefore, movement is a proper subset of copying.
The behavior of C++ in this instance is inconsistent because your code is contradictory. You want your type to be copyable but not moveable; C++ does not allow that, so it behaves oddly.
Look at what happens when you remove the contradiction. If you explicitly delete the copy constructor in ExplicitDelete, everything makes sense again. ImplicitDelete's copy/move constructors are implicitly deleted, so it is immobile. And Implicit's copy/move constructors are implicitly deleted, so it too is immobile.
If you write contradictory code, C++ will not behave in an entirely legitimate fashion.
Why does C++ compiler have more restriction on automatically generated move constructors than on automatically generated copy constructor or assignment operator ?
Automatically generated move constructors are generated only if user has defined nothing (i.e: constructor, copy, assignment, destructor..)
Copy constructor or assignment operator are generated only if user has not defined respectively copy constructor or assignment operator.
I wonder why the difference.
I believe backwards compatibility plays a big part here. If the user defines any of the "Rule of three" functions (copy ctor, copy assignment op, dtor), it can be assumed the class does some internal resource management. Implicitly defining a move constructor could suddenly make the class invalid when compiled under C++11.
Consider this example:
class Res
{
int *data;
public:
Res() : data(new int) {}
Res(const Res &arg) : data(new int(*arg.data)) {}
~Res() { delete data; }
};
Now if a default move constructor was generated for this class, its invocation would lead to a double deletion of data.
As for the move assignment operator preventing default move constructor definitions: if the move assignment operator does something other than default one, it would most likely be wrong to use the default move constructor. That's just the "Rule of three"/"Rule of five" in effect.
As far as I know, this is because of downward compatibility. Consider classes written in C++ (before C++11) and what would happen if C++11 would start to automatically generate move-ctors in parallel to existing copy-ctors or generally any other ctor. It would easily break existing code, by-passing the copy-ctor the author of that class wrote. Hence, the rules for generating a move-ctor where crafted to only apply to "safe" cases.
Here's the article from Dave Abrahams about why implicit move must go, which eventually led to the current rules of C++11.
And this is an example how it would fail:
// NOTE: This example assumes an implicitly generated move-ctor
class X
{
private:
std::vector<int> v;
public:
// invariant: v.size() == 5
X() : v(5) {}
~X()
{
std::cout << v[0] << std::endl;
}
};
int main()
{
std::vector<X> y;
// and here is where it would fail:
// X() is an rvalue: copied in C++03, moved in C++0x
// the classes' invariant breaks and the dtor will illegally access v[0].
y.push_back(X());
}
When C++ was created, it was decided that default constructor, copy-constructor, assignment-operator and destructor would be generated automatically (unless provided). Why ? Because C++ compilers should be able to compile (most) C code with identical semantics, and that's how struct work in C.
However, it was later noticed that whenever a user writes a custom destructor, she probably needs to write a custom copy-constructor/assignment-operator too; this is known as the Rule of Big Three. With hindsight, we can see that it could have been specified that the generated copy-constructor/assignment-operator/destructor would have only been generated if none of the 3 were user-provided, and it would have helped catch a lot of bugs... and still retain backward compatibility with C.
Therefore, as C++11 came around, it was decided that this time things would be done right: the new move-constructor and move-assignment-operator would only be generated automatically if it was clear that the user was not doing anything "special" with the class. Anything "special" being defined as redefining move/copy/destruction behavior.
To help with the case were people would be doing something special but still wanted "automatically generated" special methods, the = default sugar-coating was added as well.
Unfortunately, for backward compatibility reasons, the C++ committee could not go back in time and change the rules of automatic generation for copy; I wish they had deprecated it to pave the way for the next version of the Standard, but I doubt they will. it is however deprecated (see §12.8/7 for the copy constructor for example, courtesy of #Nevin).