Reputation: 151
The following code works OK:
#include <iostream>
using namespace std;
struct oops
{
~oops()
{
cout << " oops! " << endl;
}
};
struct sample
{
oops* x = nullptr;
sample(oops* p) : x(p)
{
cout << "sample: " << p << endl;
}
~sample()
{
delete x;
cout << "destroy sample " << endl;
}
sample(const sample&)
{
cout << "copy sample " << endl;
}
sample(sample&&)
{
cout << "move sample " << endl;
}
};
int main()
{
sample s = new oops;
return 0;
}
Result:
sample: 0x1470c20
oops!
destroy sample
It clearly shows that neither the move nor copy constructor was called. When these constructors are deleted,
sample(const sample&) = delete;
sample(sample&&) = delete;
gcc gives a compile error:
bpp.cpp: In function ‘int main()’:
bpp.cpp:29:17: error: use of deleted function ‘sample::sample(sample&&)’
sample s = new oops;
^
bpp.cpp:24:2: note: declared here
sample(sample&&) = delete;
^
bpp.cpp:14:2: note: after user-defined conversion: sample::sample(oops*)
sample(oops* p) : x(p)
^
Does this have anything to do with -fno-elide-constructors
?
How can I compile it without defining these constructors or using an explicit constructor?
Edit: My GCC verison is 5.4.0. The command is:
g++ bpp.cpp -std=c++17
Upvotes: 15
Views: 1592
Reputation: 10740
This line of code:
sample s = new oops;
is equivalent to writing:
sample s = sample(new oops);
In C++11 and C++14, this implicitly calls the move constructor (or the copy constructor, if no move constructor is available). Because compilers are allowed to elide copies and moves, the actual move is elided, and it doesn't show anything when the move constructor is called. Even though the actual move is elided, programs aren't allowed to refer to deleted functions even implicitly, so there's a compiler error.
You can fix this by changing the initialization to either
sample s { new oops };
or
sample s ( new oops );
or, if you really wanna use =
, you can take advantage of temporary lifetime extension to write
// s won't get destroyed until the end of the scope
// it's safe to use s after this statement
sample&& s = new oops;
C++17 made some changes to the set of value categories objects fall in. In C++17, sample(new oops)
becomes a prvalue, and the c++17 standard mandates that compilers are required to yeet prvalues into place without copying it or moving them. This is done through a combination of dark magic and sorcery.
This means that
sample s = new oops;
is legal in c++17.
-std=c++17
?This code should compile under C++17, and the error you're getting occurs because gcc 6.3 and earlier don't implement that part of the c++17 standard. This issue was fixed in gcc 7.1 and the code will compile as expected.
Upvotes: 6
Reputation: 23497
sample s = new oops;
This is a form of copy initialization. For a compiler to resolve it until C++17, a copy or move constructor must exist. However, a compiler is free to elide its call due to optimizations (with GCC and -fno-elide-constructors
, the move constructor is called).
Since C++17, neither of these constructors is required: https://wandbox.org/permlink/3V8glnpqF5QxljJl.
How can I compile it without defining these constructors or using explicit constructor?
Very simply, avoid copy initialization and use direct initialization instead:
sample s { new oops };
Or, use C++17.
Upvotes: 17