Exceptions use the statical type of an object to copy-initialize the thrown object. For instance:
struct foo
{
foo() = default;
foo(const foo&) = delete;
};
int main()
{
throw foo();
}
Clang++ --std=c++14 complains that the explicitly-deleted copy constructor can't be used. Why can't it be move-initialized instead?
It can't be move constructed because the type has no move constructor. A deleted copy constructor suppresses the implicit move constructor.
Modify the code to the following:
struct foo
{
foo() = default;
foo(const foo&) = delete;
foo(foo&&) = default;
};
int main()
{
throw foo();
}
Read this, the section "Implicitly-declared move constructor".
Because foo(foo&& ); is missing. By deleteing the copy constructor you've supressed move constructor as well.
The applicable phrasing from the standard (§[class.copy]/9) looks roughly like this (well, exactly like this, as of N4296):
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 ctor,
[...]
This applies because defining the copy ctor as deleted still means you've declared the copy ctor.
Related
When a member initializer is used in a class that has a move constructor the initilized member's constructor gets called when the enclosing class is moved. Why does this happen? Please provide references to the standard. I have a guess as to what's happening that I give with the example results below.
Also, in slightly different scenrio, why doesn't the member's constructor get called if the initialized member is a plain-old-data type?
Also, what are best practices concerning member initializers and move constructors?
#include <bits/stdc++.h>
using namespace std;
struct C {
void do_stuff(){cout<<"stuff";}
C(){cout<<"C ctor"<<endl;}
~C(){cout<<"C DTOR"<<endl;}
};
struct Foo {
ifdef MEMBER_INIT
Foo() {cout<<"Foo ctor"<<endl;};
#else
Foo() : ptr(new C) {cout<<"Foo ctor"<<endl;};
#endif
Foo(Foo &) = delete;
Foo & operator=(Foo &) = delete;
Foo & operator=(Foo &&) = delete;
Foo(Foo && rhs){cout<<"Foo MOVE ctor"<<endl; rhs.ptr.swap(this->ptr); }
~Foo(){cout << "Foo DTOR "; if(ptr) ptr->do_stuff(); cout<<endl; }
#ifdef MEMBER_INIT
unique_ptr<C> ptr = make_unique<C>();
#else
unique_ptr<C> ptr;
#endif
};
int main()
{
Foo f;
Foo f2(move(f));
}
RESULTS:
g++ -std=c++14 x.cc && ./a.out
C ctor
Foo ctor
Foo MOVE ctor
Foo DTOR stuff
C DTOR
Foo DTOR
g++ -DMEMBER_INIT -std=c++14 x.cc && ./a.out
C ctor
Foo ctor
C ctor
Foo MOVE ctor
Foo DTOR stuff
C DTOR
Foo DTOR stuff
C DTOR
Why does using the member initializer invoke another constructor call for C?
Why does using the member initializer cause the Foo destructor to run C->do_stuff()?
My quess is that member initializers get evaluated for ALL constructor types before the actual constructor (in this case a move constructor) gets run. Is that correct?
I would specifically like references in the standard that verify or contradict my guess.
When MEMBER_INIT is defined move constructor performs ptr initialization using in-class initializer and becomes
Foo(Foo && rhs): ptr{make_unique<C>()}
Otherwise it is default-initialized.
15.6.2 Initializing bases and members [class.base.init]
9 In a non-delegating constructor, if a given potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then
…
the entity is initialized from its default member initializer as specified in 11.6;
Basically you forgot to actually manually initialize ptr field by moving:
Foo(Foo && rhs): ptr{::std::move(rhs.ptr)}
class A {
public:
A() {}
A(const A& a) { cout << "A::A(A&)" << endl; }
};
class B {
public:
explicit B(A aa) {}
};
int main() {
A a;
B b(a);
return 0;
}
Why does it print "A::A(A&)"?
When was the copy constructor for "A" called? And if the code calls the copy constructor, why can I remove the copy constructor without creating a compilation error?
B(A aa) takes an A by value, so when you execute B b(a) the compiler calls the copy constructor A(const A& a) to generate the instance of A named aa in the explicit constructor for B.
The reason you can remove the copy constructor and have this still work is that the compiler will generate a copy constructor for you in cases where you have not also declared a move constructor.
Note: The compiler generated copy constructor is often not sufficient for complex classes, it performs a simple member wise copy, so for complex elements or dynamically allocated memory you should declare your own.
§ 15.8.1
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 (11.4). The latter case is deprecated if
the class has a user-declared copy assignment operator or a user-declared destructor or assignment operator.
Why the copy happens
Look at your class B c'tor:
class B {
public:
explicit B(A aa) {}
};
You receive A by value, triggering a copy during the call.
If you would have change it to (notice A & aa):
class B {
public:
explicit B(A & aa) {}
};
There wouldn't be any copy...
Default copy constructor
When you remove the c'tor, the compiler generates one for you when it can trivially do so:
First, you should understand that if you do not declare a copy
constructor, the compiler gives you one implicitly. The implicit
copy constructor does a member-wise copy of the source object.
The default c'tor is equivalent to:
MyClass::MyClass( const MyClass& other ) :
x( other.x ), c( other.c ), s( other.s ) {}
As expected the following does not compile:
class A {
public:
A() = default;
//A(A&&) = default;
A(const A&) = delete;
int x;
};
int main()
{
auto a4 = A{}; // not ok, copy constructor is deleted
auto a5 = A(); // not ok, copy constructor is deleted
return 0;
}
But if a move constructor is added, even with the copy constructor explicitly deleted, then the following does compile:
class A {
public:
A() = default;
A(A&&) = default;
A(const A&) = delete;
int x;
};
int main()
{
auto a4 = A{}; // now ok, even though copy constructor is deleted
auto a5 = A(); // now ok, even though copy constructor is deleted
return 0;
}
Why isn't the deleted copy constructor considered?
Why isn't the deleted copy constructor considered?
It is considered. It's just not used, so the fact that it's deleted doesn't matter. That rule, from [dcl.fct.def.delete] is:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
[ Note: [...] If a function
is overloaded, it is referenced only if the function is selected by overload resolution. [...] —end note ]
Overload resolution on auto a = A{}; (braces vs parens is equivalent in this case) finds two constructor candidates:
A(const A&);
A(A&& );
One of the rules for choosing which candidate is the "best viable" candidate is, from [over.match.best]:
Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if [...], and then
— [...]
— the context is an initialization by conversion function for direct reference binding (13.3.1.6) of a reference
to function type, the return type of F1 is the same kind of reference (i.e. lvalue or rvalue) as the
reference being initialized, and the return type of F2 is not
The move constructor is the same kind of reference (rvalue) as the parameter, whereas the copy constructor is not. Hence it's preferred and selected as the best viable candidate. Since A(const A&) wasn't selected by overload resolution, we're not referring to that constructor, so the code is fine.
If we actually used the copy constructor on the other hand (e.g. A a(a5)), that would actually attempt to use the copy constructor, which would be ill-formed.
As far as I know, when we have a set of overloaded functions, first the overload resolution is being performed, and only after this the access specifier is checked (such as =delete), and not the other way around. In other words, the code below
void f(int);
void f(double) = delete;
int main()
{
f(42.2); // error, use of deleted function
}
Live on coliru
produces an error, since f(double) is the best match and is deleted, and the compiler doesn't "fall back" on f(int).
I also know that marking a destructor as =delete will implicitly delete the move constructor. So, I'd expect the following code
struct Foo
{
Foo() = default;
Foo(const Foo&) = default;
~Foo() = default; // implicitly deletes the move ctor
};
int main()
{
// Why doesn't the implicitly deleted move ctor
// participate in overload resolution?
Foo mfoo = Foo{};
}
Live on Coliru
to fail, as the move constructor is deleted, but instead the compiler "falls through to copy constructor". Is this an exception in the C++ overload resolution process?
Take a look a the following code example which uses class uncopiable similar to boost::noncopyable:
#include <vector>
class uncopiable {
using self = uncopiable;
protected:
uncopiable() {}
~uncopiable() {}
uncopiable(const self&) = delete;
self& operator=(const self&) = delete;
};
struct A {
struct B : uncopiable {
using self = B;
B() {
}
B(B&&) = default;
self& operator=(B&&) = default;
~B() {
}
};
A() { v.emplace_back(); }
~A() {}
private:
std::vector<B> v;
};
int main () {}
Since I wanted to make inner class move only I explicitly specified its move constructor and assignment operator to be default ones but also since I've heard that it's a good practice to specify all of the "special member functions" in such case I inherited it from uncopiable. The problem is that compilation fails with every compiler and something similar to the following error message is displayed (this message is excerpt from the clang one):
/usr/include/c++/v1/memory:1645:31: error: call to implicitly-deleted copy constructor of 'A::B'
...
main.cpp:26:10: note: in instantiation of function template specialization 'std::__1::vector >::emplace_back<>' requested here
main.cpp:19:3: note: copy constructor is implicitly deleted because 'B' has a user-declared move constructor
It could be fixed by removing inheritance (copy operations would still not be created). But writing copy operations to be explicitly deleted inside class after that is also okay.
My questions are: why does it happen? Could it be considered a deficiency of disabling constructors/assignment operators through inheritance of helper classes?
The problem is that your uncopiable class is not moveable. Therefore the default move constructor / assignment operator of the derived class try to use the deleted copy versions.
static_assert(std::is_move_constructible<uncopiable>::value, ""); // fails
static_assert(std::is_move_assignable<uncopiable>::value, ""); // fails
The reason for this is § 12.8 ¶ 9:
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.
Declaring a copy operator or assignment operator as deleted still counts as declaring it.
The solution is of course to declare the move operations for uncopiable.
uncopiable(uncopiable&&) noexcept = default;
uncopiable& operator=(uncopiable&&) noexcept = default;
Note that the move operations should usually be declared noexcept. Especially if you want to use the type in a std::vector like in your example.
This compiles ok on MinGw:
#include <vector>
class uncopiable {
using self = uncopiable;
protected:
uncopiable() {}
~uncopiable() {}
uncopiable(const self&) = delete;
self& operator=(const self&) = delete;
};
struct A {
struct B : uncopiable {
using self = B;
B() {
}
B(B&&) {};
self& operator=(B&&) = default;
~B() {
}
};
A() { v.emplace_back(); }
~A() {}
private:
std::vector<B> v;
};
int main () {
A* a = new A();
}