我最近发现了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 with -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 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产生错误:使用删除的函数’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]:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the
object to be copied is designated by an lvalue, or when the expression in areturn
statement is a (possibly
parenthesized) id-expression that names an object with automatic storage duration declared in the body or
parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution
to select the constructor for the copy is first performed as if the object were designated by an rvalue. If
the first overload resolution fails or was not performed, or if the type of the first parameter of the selected
constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is
performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be
performed regardless of whether copy elision will occur. It determines the constructor to be called if elision
is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]
按照规则,我们执行重载解析,就像对象是右值一样.该重载决策找到T(T&&),它被明确删除.由于这个电话是不正确的,整个表达形式都是不正确的.
复制/移动省略只是一种优化.它将忽略的代码必须始终有效.