我最近发现msvc和g ++ / clang ++编译器之间存在差异,这与返回常量对象时RVO的行为有关。一个简单的例子说明了差异:
#include <iostream>
class T
{
public:
T() { std::cout << "T::T()\n"; }
~T() { std::cout << "T::~T()\n"; }
T(const T &t) { std::cout << "T::T(const T&)\n"; }
T(T &&t) { std::cout << "T::T(T&&)\n"; }
T(const T &&t) { std::cout << "T::T(const T&&)\n"; }
};
const T getT()
{
T tmp;
return tmp;
}
int main()
{
T nonconst = getT();
}
启用优化后,两个示例都只会产生T()和~T()调用,这是由于RVO(它忽略了返回类型的常量)所预期的。但没有它们,结果会有所不同。
按照规则使用-fno-elide-constructors
clang ++或g ++:
T::T()
T::T(T&&) // from non-const local tmp variable to temp storage (const, due to return-type)
T::~T()
T::T(const T&&) // from constant temp storage to nonconst variable
T::~T()
T::~T()
msvc(2013)忽略了返回类型的常量:
T::T()
T::T(T&&) // from local non-const tmp var to non-const nonconst var
T::~T()
T::~T()
稍作修改:
const T getT()
{
const T tmp; // here const is added
return tmp;
}
clang ++或g ++ with -fno-elide-constructors
,一切都如预期的那样:
T::T()
T::T(const T&&) // from const local tmp var to temp storage (const, due to return-type)
T::~T()
T::T(const T&&) // from constant temp storage to nonconst variable
T::~T()
T::~T()
msvc(2013):
T::T()
T::T(const T&&) // from local const tmp var to non-const nonconst var
T::~T()
T::~T()
所有这些都解释了原始版本中的下一个问题(const
没有tmp
):如果禁止使用常量临时构造,例如T(const T &&t) = delete;
g ++ / clang ++产生错误:{ {1}}和msvc没有。
那么,是MSVC中的一个错误吗?(它忽略了返回类型规范并打破了建议的语义)
简而言之:msvc编译以下代码,g ++ / clang ++不编译。
use of deleted function ‘T::T(const T&&)’
答案 0 :(得分:1)
我相信modules/module2/app/views
在这里是一个红鲱鱼。我们可以将示例简化为:
const
这无法在gcc或clang上编译,我相信失败是正确的。无论复制省略是否发生,我们仍然对构造函数执行重载决策。来自[class.copy]:
当满足复制/移动操作的省略标准时,但不符合异常声明,以及 要复制的对象由左值指定,或者在
struct T { T() = default; T(T &&) = delete; }; T getT() { T tmp; return tmp; } int main() { T x = getT(); }
语句中的表达式是(可能) 括号中的 id-expression ,用于在主体中声明自动存储持续时间的对象 最里面的封闭函数的 parameter-declaration-clause 或 lambda-expression ,重载解析 首先执行选择复制的构造函数,就好像对象是由rvalue指定的一样。如果 第一个重载决策失败或未执行,或者如果所选的第一个参数的类型 构造函数不是对象类型的rvalue引用(可能是cv-qualified),重载决策是 再次执行,将对象视为左值。 [注意:这个两阶段的重载决议必须是 无论是否会发生复制,都会执行。如果是elision,它确定要调用的构造函数 未执行,即使呼叫被省略,也必须可以访问所选的构造函数。 -end note]
遵循规则,我们执行重载解析,就像对象是右值一样。该重载决策找到return
,明确T(T&& )
d。由于这个电话形成不良,整个表达形式都是不正确的。
复制/移动省略只是一种优化。它所忽略的代码必须始终有效。