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

C++ 从临时对象返回常量对象和构造,c++,c++11,visual-c++,g++,clang++,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(con

我最近发现了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();
}
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;
}
使用
-fno elide构造函数的clang++或g++再次符合预期:

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年):

所有这些都解释了原始版本中的下一个问题(对于
tmp
,没有
const
):如果禁止从常量临时变量进行构造,如
T(const T&&T)=删除g++/clang++产生错误:
使用已删除的函数“T::T(const T&&)”
而msvc没有。

所以,这是MSVC中的一个bug吗?(它忽略了返回类型规范并破坏了建议的语义)

简而言之: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
}
#包括
T类
{
公众:

T(){std::cout我认为
常量在这里是一个麻烦。我们可以将示例简化为:

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表达式的参数声明子句,重载解析 要选择副本的构造函数,首先将其视为对象由右值指定。如果 第一个重载解析失败或未执行,或者如果选择的第一个参数的类型 构造函数不是对对象类型的右值引用(可能是cv限定的),重载解析为 再次执行,将对象视为左值。[注意:此两阶段重载解析必须 无论是否会发生复制省略,都将执行。它确定在发生复制省略时要调用的构造函数 未执行,并且即使省略调用,所选构造函数也必须可访问。-结束说明]

按照规则,我们执行重载解析,就像对象是一个右值一样。重载解析发现
T(T&&)
,这是显式的
delete
d。由于该调用的格式不正确,整个表达式的格式也不正确


复制/移动省略仅仅是一种优化。它将省略的代码必须首先有效。

我有一种模糊的感觉,即在复制省略和考虑右值引用中限定符的作用在C++11问世后被修改了;也许每个编译器都遵循不同级别的缺陷报告?哪个版本(包括service pack版本)您使用MSVC吗?这里我使用VS2013+SP5。最后一个代码段失败(如预期的那样),错误为C2280:
T::T(const T&&)“:试图引用已删除的函数。
。Microsoft还列出了RVO@HelloWorld的限制,是的,我的版本有些过时。@HelloWorld,我已安装了第5个更新包。剪断的部分突出显示为错误,但代码仍然成功生成(生成/生成解决方案)UpDebug,复制构造函数的有效性对于C++中的复制删除已经是正确的98@Barry好的,但整个问题是关于不同的事情:重载解析由每个编译器执行,但最终它们使用不同的选择:常量和非常量右值-constructors@AlexanderSergeev显然,如果MSVC正在推导一个非常量ref关于常量对象,这是一个bug…@Barry,有趣的是,返回一个对象包括两个阶段,中间一个是声明的返回类型。应该进行哪个重载解析?构造返回类型对象还是最终目的地?因为msvc检查从命名的本地va构造最终对象的可用性riable;和clang/gcc还检查从返回类型对象到目标对象的构造。
#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
}
struct T
{
    T() = default;
    T(T &&) = delete;
};

T getT()
{
    T tmp;
    return tmp;
}

int main()
{
    T x = getT();
}