Возможно ли вернуть экземпляр недвижного, не скопированного типа?
В 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
могут заменить это.
Ответы
Ответ 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