Подтвердить что ты не робот

Возможно ли вернуть экземпляр недвижного, не скопированного типа?

В VS2013 обновлении 5 у меня есть это:

class Lock
{
public:
    Lock(CriticalSection& cs) : cs_(cs)
    {}

    Lock(const Lock&) = delete;
    Lock(Lock&&) = delete;
    Lock& operator=(const Lock&) = delete;
    Lock& operator=(Lock&&) = delete;

    ~Lock()
    {
        LeaveCriticalSection(&(cs_.cs_));
    }

private:
    CriticalSection& cs_;
};


class CriticalSection
{
    CRITICAL_SECTION cs_;
public:
    CriticalSection(const CriticalSection&) = delete;
    CriticalSection& operator=(const CriticalSection&) = delete;
    CriticalSection(CriticalSection&&) = delete;
    CriticalSection& operator=(CriticalSection&&) = delete;

    CriticalSection()
    {
        InitializeCriticalSection(&cs_);
    }

    ~CriticalSection()
    {
        DeleteCriticalSection(&cs_);
    }

    // Usage:  auto lock = criticalSection.MakeLock();
    Lock MakeLock()
    {
        EnterCriticalSection(&cs_);
        return Lock(*this);
    }
}

MakeLock возвращает экземпляр недвижного, не копируемого типа. И это, похоже, работает нормально. Но Visual Studio intellisense подчеркивает возврат в красный цвет с предупреждением о невозможности ссылки на конструктор блокировки перемещения, поскольку это удаленная функция.

Я пытаюсь понять, почему это работает, и если оно стандартно соответствует С++ или просто что-то своеобразное для MSVC. Я думаю, что возвращение работает, потому что необходимость в построении возвращаемого значения может быть оптимизирована, поэтому предупреждение intellisense предупреждает о чем-то, что на самом деле - на самом деле не происходит.

Я думаю, что я где-то читал, что С++ будет стандартизировать, гарантируя, что оптимизация возвращаемого значения всегда будет выполняться.

Итак, соответствует ли этот код С++ и будет ли он работать в будущих компиляторах?

P.S. Я понимаю, что std::mutex и std::lock_guard могут заменить это.

4b9b3361

Ответ 1

Если это компилируется, это ошибка в компиляторе. VC2015 правильно не скомпилирует его.

class Foo
{
public:
    Foo() {}
    Foo(const Foo&) = delete;
    Foo(Foo&&) = delete;
};


Foo Bar()
{
    return Foo();
}

Дает мне:

xxx.cpp(327): error C2280: 'Foo::Foo(Foo &&)': attempting to reference a deleted function

и g++ 4.9 говорит:

error : use of deleted function 'Foo::Foo(Foo&&)'

В стандарте очень ясно, что конструктор или конструктор перемещения должен существовать и быть доступен, даже если RVO означает, что он не вызывается.

Ответ 2

В С++ 17 код в ответе Мартина Боннера является законным.

Компилятор не только разрешен, но и обязан удалить копию. Живые примеры для Clang и GCC. С++ 17 6.3.2/2 (выделено мной):

[...] [Примечание: оператор return может включать вызов конструктора для выполнения копирования или перемещения операнда , если он не является prvalue или если его тип отличается от типа возвращаемого значения функция. Операция копирования, связанная с оператором возврата, может быть исключена или преобразована в операцию перемещения, если возвращается переменная продолжительности автоматического хранения (10.9.5). - конец примечания]

Prvalue здесь означает столько же, сколько временное. Точные определения и множество примеров см. в здесь.

В С++ 11 это действительно незаконно. Но это легко исправить, используя инициализацию фигурных скобок в операторе return, вы создаете возвращаемое значение в расположении сайта вызова, полностью обойдя требование конструктора копирования, который часто используется. С++ 11 6.6.3/2:

[...] Оператор return с фигурным списком инициализации инициализирует объект или ссылку, которые должны быть возвращены из функции путем копирования-инициализации списка (8.5.4) из указанного списка инициализатора. [...]

Copy-list-initialization означает, что вызывается только конструктор. Конструкторы копирования/перемещения не участвуют.

Живые примеры для Clang и GCC. Начиная с версии компилятора Visual Studio 16.14 и выше, установка правильного языкового стандарта позволяет компилировать этот код.

Возврат не копируемых объектов, подобных этому, является очень мощной конструкцией для возврата, например. std::lock_guard из функций (что позволяет вам легко сохранять член std::mutex закрытым) и т.д.

Ответ 3

Начиная с MSVC v19.14, MSVC (он же Visual Studio) также реализует поведение С++ 17, позволяя return не копируемого, неподвижного объекта в случаях, когда применяется RVO: https://godbolt.org/z/fgUFdf

Как показывает ответ rubenvb, это означает, что GCC, Clang и MSVC поддерживают такое поведение С++ 17: https://godbolt.org/z/Hq_GyG