Sco*_*ham 17 c++ language-lawyer
在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);
}
}
Run Code Online (Sandbox Code Playgroud)
MakeLock返回不可移动,不可复制类型的实例.这似乎工作正常.但是,Visual Studio intellisense确实强调了红色的返回,并警告说Lock的移动构造函数不能被引用,因为它是一个已删除的函数.
我试图理解为什么这有效,如果它是标准符合C++或只是MSVC特有的东西.我猜回归是有效的,因为构建返回值的需要可以被优化掉,所以intellisense警告会警告某些事情 - 实际上 - 实际上不会发生.
我想我在某处读到C++会标准化以确保返回值优化总是会发生.
那么,这个符合C++的代码是否会继续在未来的编译器中运行?
PS我意识到std::mutex并std::lock_guard可能取代它.
如果编译,则编译器中存在错误.VC2015无法正确编译它.
class Foo
{
public:
Foo() {}
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
};
Foo Bar()
{
return Foo();
}
Run Code Online (Sandbox Code Playgroud)
给我:
xxx.cpp(327): error C2280: 'Foo::Foo(Foo &&)': attempting to reference a deleted function
Run Code Online (Sandbox Code Playgroud)
和g ++ 4.9说:
error : use of deleted function 'Foo::Foo(Foo&&)'
Run Code Online (Sandbox Code Playgroud)
标准非常清楚,复制构造函数或移动构造函数必须存在且可访问,即使RVO意味着它未被调用.
在C++ 17中,Martin Bonner的答案中的代码是合法的.
编译器不仅被允许,而且还有义务忽略该副本.Clang和GCC的实例.C++ 17 6.3.2/2(强调我的):
[...] [注意:如果操作数不是prvalue或者其类型与函数的返回类型不同,则返回语句可能涉及调用构造函数以执行操作数的复制或移动.如果返回自动存储持续时间变量(10.9.5),则可以省略与返回语句相关联的复制操作或将其转换为移动操作. - 结束说明]
这里的prvalue意味着一个临时的.有关确切的定义和大量示例,请参见此处.
在C++ 11中,这确实是非法的.但它很容易解决,通过在return语句中使用大括号初始化,您可以在调用站点位置构造返回值,完美地绕过了经常被省略的复制构造函数的要求.C++ 11 6.6.3/2:
[...]带有braced-init-list的return语句通过copy-list-initialization(8.5.4)从指定的初始化列表初始化要从函数返回的对象或引用.[...]
复制列表初始化意味着只调用构造函数.不涉及复制/移动构造函数.
不幸的是,它既不适用于Visual Studio编译器的任何最新版本,也很难受,并且保持其糟糕的C++一致性声誉.返回像这样的非可复制对象是一个非常强大的构造,用于std::lock_guard从函数返回例如s(允许您轻松地保持std::mutex成员私有)等.