0

我做错了什么(再次)?

#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)
4

5 回答 5

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

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

使函数更复杂,使编译器无法使用优化,你会看到在行动。例如:

Map createMap()
{
    Map a, b;
    if (rand())
        return a;
    return b;
}
于 2010-10-30T15:55:45.637 回答
3

您正在声明一个函数,而不是一个对象:

T name (T(blah));

相当于:

T name(T blah);

可以识别为函数声明。您可以使用额外的括号:

Map m2 ((Map(m1)));

这被称为最令人头疼的解析

于 2010-10-30T15:32:32.790 回答
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,您可以使用它来更好地理解移动语义。

于 2010-10-30T16:01:32.610 回答
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 上的预期输出。

于 2010-10-30T16:20:42.540 回答
0
Map createMap()
{
    return Map();
}

我认为编译器会在上面进行 RVO (返回值优化),因此不会创建临时对象。

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

Map createMap()
{
    Map m;
    m.DoSomething(); // this should make the compiler stop doing RVO
    return m;
}
  • 一些编译器会执行 RVO,而不管编译器设置(调试/发布模式)如何 - 例如 bcc32。我有一种感觉 VC 会是一样的。
于 2010-11-01T10:56:56.017 回答