移动ctor没有被调用

时间:2010-10-30 15:22:33

标签: c++ c++11 most-vexing-parse

我做错了什么(又一次)?

#include <iostream>
using std::cout;

struct Map
{
    Map()
    {
        cout << "Map()\n";
    }
    Map(const Map& pattern)
    {
        cout << "Map(const Map& pattern)\n";
    }
    Map(Map&& tmp)
    {
        cout << "Map(Map&& tmp)\n";
    }
};

Map createMap()
{
    return Map();
}

int main(int argc, char* argv[])
{
    //dflt
    Map m;
    //cpy
    Map m1(m);
    //move
    Map m2(Map(m1));//<<I thought that I create here tmp unnamed obj.
    Map m3(createMap());//<<---or at least here, but nope...
    return 0;
}

请参阅代码中的注释行

已编辑[摘自FredOverflow答案]

int main() 
{ 
    std::cout << "default\n"; 
    Map m; 

    std::cout << "\ncopy\n"; 
    Map m1(m); 

    std::cout << "\nmove\n";
    Map m2((Map(m1))); 

    std::cout << "\nmove\n"; 
    Map m3(createMap()); 
}  

我收到了输出:

default
Map()

copy
Map(const Map& pattern)

move
Map(const Map& pattern)//Here why not move ctor aswell as copy?

move
Map()
Map()
Map(Map&& tmp)

5 个答案:

答案 0 :(得分:4)

Map m3(createMap());//<<---or at least here, but nope...

您正在看到返回值优化的实际效果。在C ++中,允许编译器优化复制返回的对象,并让函数直接与存储结果的调用者对象一起工作。也没有必要调用移动构造函数。

使函数更复杂,因此编译器无法使用优化,您将看到移动操作。例如:

Map createMap()
{
    Map a, b;
    if (rand())
        return a;
    return b;
}

答案 1 :(得分:3)

你宣布一个功能,而不是一个对象:

T name (T(blah));

相当于:

T name(T blah);

可以识别为函数声明。你可以使用额外的parens:

Map m2 ((Map(m1)));

这称为most vexing parse

答案 2 :(得分:1)

我稍微修改了您的main例程以更好地理解输出:

int main()
{
    std::cout << "default\n";
    Map m;

    std::cout << "\ncopy\n";
    Map m1(m);

    std::cout << "\nmove\n";
    Map m2(Map(m1));

    std::cout << "\nmove\n";
    Map m3(createMap());
}

这是g++ -fno-elide-constructors的输出:

default
Map()

copy
Map(const Map& pattern)

move

move
Map()
Map(Map&& tmp)
Map(Map&& tmp)

正如其他人已经指出的那样,Map m2(Map(m1));确实是一个函数声明,因此你没有输出。第二步是被解释为函数声明,因为createMap不是类型名称。这里涉及两个移动构造函数。一个人将通过评估Map()创建的临时对象移动到通过评估createMap()创建的临时对象,第二个移动从后者初始化m3。这正是人们所期望的。

如果您通过编写Map m2((Map(m1)));来修复第一步,则输出变为:

move
Map(const Map& pattern)
Map(Map&& tmp)

同样,没有惊喜。通过评估Map(m1)来调用复制构造函数,然后将该临时对象移动到m2。如果您在没有-fno-elide-constructors的情况下进行编译,则移动操作会消失,因为它们会被更有效的优化(如RVO或NRVO)取代:

default
Map()

copy
Map(const Map& pattern)

move
Map(const Map& pattern)

move
Map()

我确信Visual C ++有一个类似于-fno-elide-constructors的编译器选项,您可以使用它来更好地理解移动语义。

答案 3 :(得分:1)

此程序显示预期输出。

#include <iostream>
using std::cout;

struct Map
{
    Map()
    {
        cout << "Map()\n";
    }
    Map(const Map& pattern)
    {
        cout << "Map(const Map&)\n";
    }
    Map(Map&& tmp)
    {
        cout << "Map(Map&&)\n";
    }
};

Map createMap()
{
    Map m;
    return m;
}

int main(int argc, char* argv[])
{
    //dflt
    Map m;
    //cpy
    Map m1(m);
    //move
    Map m2 = createMap();//<<I thought that I create here tmp unnamed obj.
    std::cin.get();
    return 0;
}

请注意对createMap()的更改。它不使用直接临时值,而是使用命名返回值。此程序显示Visual Studio 2010上的预期输出。

答案 4 :(得分:0)

Map createMap()
{
    return Map();
}

我认为编译器会在上面做一个RVO (return value optimization),因此不会创建临时版。

如果您将其更改为以下内容,您应该会看到您的移动ctor被调用。

Map createMap()
{
    Map m;
    m.DoSomething(); // this should make the compiler stop doing RVO
    return m;
}
  • 无论编译器设置如何(调试/释放模式),某些编译器都会执行RVO - 例如bcc32。我觉得VC会是一样的。