从临时

时间:2015-09-18 18:30:35

标签: c++ c++11 visual-c++ g++ clang++

我最近发现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&&)’

1 个答案:

答案 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。由于这个电话形成不良,整个表达形式都是不正确的。

复制/移动省略只是一种优化。它所忽略的代码必须始终有效。