std :: pair移动没有在定义上省略?

Mar*_* Ba 5 c++ copy-elision std-pair visual-c++-2012

我注意到Visual Studio 2012中出现了一些非常奇怪的事情:定义一对像这样的对象:

    auto objp = pair<int, LogMe>();
Run Code Online (Sandbox Code Playgroud)

不会的Elid一对在VC11的复制/移动,这个调用会打印:

LogMe::LogMe - def.ctor!
LogMe::LogMe - move.ctor!
LogMe::~LogMe - dtor!
Run Code Online (Sandbox Code Playgroud)

也就是说,创建一个临时对,然后其移动到objp变量中.(声明pair<...> obj;它只记录默认的ctor)

我单独使用LogMe测试对象进行交叉检查:

cout << "# Construct Object via auto obj = ...\n";
auto obj = LogMe();

# Construct Object via auto obj = ...
LogMe::LogMe - def.ctor!
Run Code Online (Sandbox Code Playgroud)

这里的任务将被删除.

这似乎是VC11特有的,因为在IDEOne(使用gcc 4.8.1)中测试它表明,无关紧要的移动总是在那里被省略.

这里发生了什么? 不能依赖被删除的初始化副本让我感到紧张.

注意:发布与调试版本的测试显示相同的结果.(我原本期望,因为复制省略与MSVC中的优化标志无关.)


要测试的完整源代码(另请参阅ideone链接):

#include "stdafx.h"
#include <iostream>
#include <map>

using namespace std;

struct LogMe {
    std::string member;

    LogMe() {
        cout << __FUNCTION__ << " - def.ctor!" << endl;
    }
    ~LogMe() {
        cout << __FUNCTION__ << " - dtor!" << endl;
    }
    LogMe(LogMe const&) {
        cout << __FUNCTION__ << " - cpy.ctor!" << endl;
    }
    LogMe& operator=(LogMe const&) {
        cout << __FUNCTION__ << " - cpy.assign.op!" << endl;
        return *this;
    }
    LogMe(LogMe&&) {
        cout << __FUNCTION__ << " - move.ctor!" << endl;
    }
    LogMe& operator=(LogMe&&) {
        cout << __FUNCTION__ << " - move.assign.op!" << endl;
        return *this;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    {
        cout << "# Construct Object via auto obj = ...\n";
        auto obj = LogMe();
        cout << "# Construct pair<int, object> via auto objp = ...\n";
        auto objp = pair<int, LogMe>();
        cout << "# Construct pair<int, object> via pair objp2; ...\n";
        pair<int, LogMe> p2;
    }
    return 0;
Run Code Online (Sandbox Code Playgroud)

Mar*_* Ba 2

看来不是移动向量,也不是模板化移动向量导致了问题,而是enable_if<is_convertable<...模板化移动向量中存在:

仅使用一个对象进行测试,抛出autopair退出测试:

并且,通过这样的测试:

    cout << "# Construct Object: LogMeTempl obj = LogMeTempl();\n";
    LogMeTempl obj = LogMeTempl();
    cout << "# Construct Object: LogMeTempl obj2;\n";
    LogMeTempl obj2;
Run Code Online (Sandbox Code Playgroud)
  • 好的,复制移动也被省略了:

    template<class Other>
    LogMeTempl(Other&& rhs
    //      , typename enable_if<is_convertible<Other, LogMeTempl>::value, void>::type ** = 0
    ) {
        cout << __FUNCTION__ << ...;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 失败!调用移动ctor!

    template<class Other>
    LogMeTempl(Other&& rhs
            , typename enable_if<is_convertible<Other, LogMeTempl>::value, void>::type ** = 0
    ) {
        cout << __FUNCTION__ << ...;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,enable_if 可以减少为enable_if<true, void>::type** = 0- 如果事实上任何其他默认参数都可以(例如, int defaulted_param_on_move_ctor = 0,它仍然会阻止 move elision)。

    这也扩展到仅具有默认参数的复制因子的类型。也不会被删掉。与 gcc 的快速交叉检查表明那里似乎没有任何这样的问题。

简答

在其复制/移动构造函数中具有默认参数的类型不会删除其初始化复制/移动。

我已针对此问题在 MS.connect 上添加了一个错误。

我还在IDEone 中添加了 (N)RVO 的测试用例。即使没有默认参数,*N*RVO 在 gcc 中似乎也比 VC++ 中工作得更好。