繁体   English   中英

从临时返回常量对象和构造

[英]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()

所有这些都解释了原始版本中的下一个问题(没有consttmp ): 如果禁止构造来自常量临时,如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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM