[英]returning constant object and construction from temporary
我最近发现了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(这会忽略返回类型的常量)所期望的。 但没有它们,结果会有所不
clang ++或g ++与-fno-elide-constructors
规则的一切:
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)忽略了返回类型constness:
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 ++与-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 ++产生错误: use of deleted function 'T::T(const T&&)'
而msvc没有。
那么, 这是MSVC中的一个错误吗? (它忽略了返回类型规范并打破了建议的语义)
简而言之:msvc编译以下代码,g ++ / clang ++没有。
#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) = delete;
};
const T getT()
{
const T tmp;
return tmp;
}
int main()
{
T nonconst = getT(); // error in gcc/clang; good for msvc
}
我相信const
在这里是一个红鲱鱼。 我们可以将示例简化为:
struct T
{
T() = default;
T(T &&) = delete;
};
T getT()
{
T tmp;
return tmp;
}
int main()
{
T x = getT();
}
这无法在gcc或clang上编译,我相信失败是正确的。 无论复制省略是否发生,我们仍然对构造函数执行重载决策。 来自[class.copy]:
当满足复制/移动操作的省略标准时,但不满足异常声明 ,并且要复制的对象由左值指定,或者当
return
语句中的表达式是(可能带有括号的) id-时表达式 ,用于在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的具有自动存储持续时间的对象,首先执行重载决策以选择复制的构造函数,就像对象由rvalue指定一样。 如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值。 [注意:无论是否发生复制省略,都必须执行此两阶段重载决策。 如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数。 - 尾注]
按照规则,我们执行重载解析,就像对象是右值一样。 该重载决策找到T(T&& )
,它明确地delete
d。 由于这个电话是不正确的,整个表达形式都是不正确的。
复制/移动省略只是一种优化。 它将忽略的代码必须始终有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.