I created this base class for non-copyable classes:
class non_copyable
{
public:
non_copyable(const non_copyable&) = delete;
non_copyable& operator=(const non_copyable&) = delete;
virtual ~non_copyable() = default;
protected:
non_copyable() = default;
};
Then I created this derived class:
class manager
: public non_copyable
{
public:
manager()
{
}
std::string s;
};
I'm able to create an instance of the class and return it like this:
manager get()
{
return manager();
}
I think this should not be possible, because the copy constructor is deleted and the implicitly generated move constructor is deleted, because there is a user-defined (deleted) copy constructor.
This code compiles with MinGW-64 7.2 but not with MSVC 2017 and yields this message:
function "manager::operator=(const manager &) throw()" (declared implicitly) cannot be referenced -- it is a deleted function
Is this a problem with the compiler, allowed by C++ design or am I doing something wrong?
In C++17, this operation requires neither a move nor a copy; the entire thing is elided.
As such, Visual Studio is either wrong or incomplete in its implementation of this language standard.†
In general try not to alter C++'s own semantics. Preventing expensive things is fine, but preventing free things is a step too far IMO.
† Depending on your exact version, this blog post may be relevant — it states that they tried to make this feature work, but it was too broken, so they rolled it back for now; I do not know whether there is a more recent version that implements it.
In c++17 prvalue expressions are not logically objects. In c++14 they are, logically, objects (or rather they create them).
Now, prvalue expressions are instructions on how to make an object. In certain circumstances those instructions are applied to create temporary or non-temporary objects.
This is more popularly known as "guaranteed elision". But really it just removes any need of elision in many cases (not all).
manager get() {
return manager();
}
In c++14 manager()
was a prvalue expression that creates an object. The return value is another prvalue. The copy or move from manager()
to the return value of get
could be elided by these two objects having their identity and lifetime merged.
In c++17 manager()
is a prvalue, as is the return value of get()
. You don't "copy" or "move" instructions on how to create an object, and neither are objects. The return just tells the return value "here are the instructions you need".
manager foo = get();
here we construct foo
from a prvalue -- from instructions on how to make a manager
. No temporary object is created; rather, we just construct the object as instructed by the prvalue return of get()
.
In c++14 we'd instead have a temporary manager object whose lifetime could be elided with the named object foo
.
The effects of this kind of elision and using prvalues "directly" are very similar at runtime, but one involves logical move or copy constructor calls we later eliminate, the other one never had a 2nd object to begin with.
Asto why MSVC2017 acts differently, their implementation of c++11 remains incomplete (in admittedly smaller ways each year, but I still get burned) let alone c++17.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With