【问题标题】:std::pair move not elided on definition?std::pair 移动未在定义中省略?
【发布时间】:2013-10-09 21:17:18
【问题描述】:

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

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

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

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

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

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

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

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

这里的分配将被省略。

这似乎是 VC11 特有的,因为testing it in IDEOne(它使用 gcc 4.8.1)表明无关的移动总是在那里被省略。

这是怎么回事? 无法依赖被忽略的初始化副本让我很紧张。

注意:发布与调试版本的测试显示相同的结果。 (这是我所预料的,因为复制省略是独立于 MSVC 中的优化标志执行的。)


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

#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;

【问题讨论】:

  • 我认为pair 构造函数可能类似于pair( T&amp;&amp; = type1(), U&amp;&amp; = type2() ) - 即,它可能在创建单个对时创建临时对象。但事实并非如此——有一个显式的平凡构造函数。下一个问题是,您是否在调试中这样做?
  • @Yakk - 见我的笔记。调试与发布没有区别
  • 呸,错过了。 auto 重要吗?
  • @Yakk - 我想我看到auto 没关系。一旦我用 VC11 回到机器上,我将不得不重新检查。
  • 嗯...我用 VS2005 测试了这个(没有移动也没有自动),那里的副本也总是被忽略。

标签: c++ std-pair visual-c++-2012 copy-elision


【解决方案1】:

看来不是移动 ctor 或模板化移动 ctor 导致问题,而是模板化移动 ctor 中存在 enable_if&lt;is_convertable&lt;...

仅使用一个对象进行测试,将 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&lt;true, void&gt;::type** = 0 - 如果事实 any 额外的默认参数会起作用(例如 , int defaulted_param_on_move_ctor = 0,它仍然会防止移动省略)。

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

简答

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

我为此问题添加了bug on MS.connect

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

【讨论】:

  • 整洁!所以有两个问题:首先,他们必须使用骇人听闻的“带有人为类型的默认 nullptr”来启用 SFINAE,其次是因为省略优化被其他默认参数阻止(我认为这是不正确的)。在 C++11(和 MSVC2013)中,他们可以在 ctor 中使用默认模板参数来执行此操作,而无需构造函数的默认参数。我不知道 MSVC2013 std C++ 库是否有这种改进,但 MSVC 2013 有编译器支持blogs.msdn.com/b/vcblog/archive/2013/06/28/…
猜你喜欢
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 2011-05-05
  • 1970-01-01
  • 2016-04-13
  • 2018-12-15
  • 1970-01-01
相关资源
最近更新 更多