Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does g++ allow returning non-copyable class? [duplicate]

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?

like image 658
lorisleitner Avatar asked Mar 12 '18 20:03

lorisleitner


2 Answers

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.

like image 126
Lightness Races in Orbit Avatar answered Nov 13 '22 01:11

Lightness Races in Orbit


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.

like image 41
Yakk - Adam Nevraumont Avatar answered Nov 13 '22 01:11

Yakk - Adam Nevraumont