In VS2013 update 5, I've got this:
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
returns an instance of a non-movable, non-copyable type. And this seems to work ok. But, Visual Studio intellisense does underline the return in red with a warning that Lock's move constructor can't be referenced as it is a deleted function.
I'm trying to understand why this works and if it is standard conforming C++ or just something peculiar to MSVC. I guess the return works because the need to construct the returned value can be optimized away, so the intellisense warning warns about something that doesn't - in practice - actually happen.
I think I read somewhere that that C++ would standardize on ensuring that return value optimizations would always happen.
So, is this conforming C++ code and will it continue to work in future compilers?
P.S. I realize std::mutex
and a std::lock_guard
might replace this.
If that compiles, it is a bug in the compiler. VC2015 correctly fails to compile it.
Gives me:
and g++ 4.9 says:
The standard is very clear that a copy constructor or move constructor must exist and be accessible, even if RVO means it is not invoked.
As of MSVC v19.14, MSVC (aka Visual Studio) also implements the C++17 behavior of allowing
return
of a non-copyable, non-movable object in cases where the RVO applies: https://godbolt.org/z/fgUFdfAs rubenvb's answer indicates, this means that GCC, Clang, and MSVC all support this C++17 behavior: https://godbolt.org/z/Hq_GyG
In C++17, the code in Martin Bonner's answer is legal.
The compiler is not only permitted, but also obligated to elide the copy. Live examples for Clang and GCC. C++17 6.3.2/2 (emphasis mine):
A prvalue here means as much as a temporary. For exact definitions and a lot of examples, see here.
In C++11, this is indeed illegal. But it is easily remedied, by using brace initialization in the return statement, you construct the return value at the call site location, perfectly bypassing the requirement of an often-elided copy constructor. C++11 6.6.3/2:
Copy-list-initialization means that only the constructor is called. No copy/move constructors are involved.
Live examples for Clang and GCC. From Visual Studio compiler version 16.14 up, setting the correct language standard allows this code to be compiled.
Returning non-copyable objects like this is a very powerful construct for returning e.g.
std::lock_guard
s from functions (allowing you to easily keep astd::mutex
member private) etc.