6

我注意到 Visual Studio 2012 有一些很奇怪的地方: 像这样定义对对象:

    auto objp = pair<int, LogMe>();

不会在 VC11 中忽略该对的复制/移动,此调用将打印

LogMe::LogMe - def.ctor!
LogMe::LogMe - move.ctor!
LogMe::~LogMe - dtor!

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

我已经单独与我的 LogMe 测试对象进行了交叉检查:

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

# Construct Object via auto obj = ...
LogMe::LogMe - def.ctor!

这里的任务将被省略。

这似乎是 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;
4

1 回答 1

2

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

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

  • 好的,复制/移动省略:

            cout << "# Construct Object: auto obj = LogMe();\n";
            LogMe obj = LogMe();
    
            LogMe(LogMe&&) {
                cout << __FUNCTION__ ...
            }
    

而且,通过这样的测试:

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

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

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

    并注意 enable_if 可以减少到enable_if<true, void>::type** = 0- 如果事实上任何额外的默认参数都可以(例如, int defaulted_param_on_move_ctor = 0,它仍然会阻止移动省略)。

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

简答

在其复制/移动 ctor 中具有默认参数的类型不会省略其初始化复制/移动。

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

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

于 2013-10-10T19:30:29.620 回答