将临时对象移动到矢量中

BЈо*_*вић 9 c++ object-lifetime c++11

#include <iostream>
#include <utility>
#include <vector>

int i = 0;
struct A
{
    A() : j( ++i )
    {
        std::cout<<"constructor "<<j<<std::endl;
    }
    A( const A & c) : j(c.j)
    {
        std::cout<<"copy "<<j<<std::endl;
    }
    A( const A && c) : j(c.j)
    {
        std::cout<<"move "<<j<<std::endl;
    }
    ~A()
    {
        std::cout<<"destructor "<<j<<std::endl;
    }

    int j;
};

typedef std::vector< A > vec;

void foo( vec & v )
{
    v.push_back( std::move( A() ) );
}

int main()
{
    vec v;

    foo( v );
    foo( v );
}
Run Code Online (Sandbox Code Playgroud)

上面的例子产生下一个输出:

constructor 1
move 1
destructor 1
constructor 2
move 2
move 1
destructor 1
destructor 2
destructor 1
destructor 2
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 为什么第一个析构函数被执行(但是没有为第二个对象执行)?
  2. 为什么在移动第一个对象之前执行第二个对象的移动?
  3. 为什么最后两个析构函数被执行?

PS我刚刚检查过,并且对象确实按预期放置(第一个进入向量中的位置0,第二个进入向量中的位置1)

PPS如果重要,我正在使用gcc 4.3,我编译这样的程序:

g++ n1.cpp -Wall -Wextra -pedantic -ansi -std=c++0x -O3
Run Code Online (Sandbox Code Playgroud)

How*_*ant 10

我稍微记录了你的例子:

#include <iostream>
#include <utility>
#include <vector>

int i = 0;
struct A
{
    A() : j( ++i )
    {
        std::cout<<"constructor "<<j<<std::endl;
    }
    A( const A & c) : j(c.j)
    {
        std::cout<<"copy "<<j<<std::endl;
    }
    A( A && c) : j(c.j)
    {
        std::cout<<"move "<<j<<std::endl;
    }
    ~A()
    {
        std::cout<<"destructor "<<j<<std::endl;
    }

    int j;
};

typedef std::vector< A > vec;

void foo( vec & v )
{
    v.push_back( A() );
}

int main()
{
    vec v;
    std::cout << "A\n";
    foo( v );
    std::cout << "B\n";
    foo( v );
    std::cout << "C\n";
}
Run Code Online (Sandbox Code Playgroud)
  1. 我已经const从移动构造函数中删除了.
  2. 我已经删除了std::movepush_back(它是多余的).
  3. 我在调用之间插入了标记foo.

对我来说,这打印出类似于你的代码:

A
constructor 1
move 1
destructor 1
B
constructor 2
move 2
copy 1
destructor 1
destructor 2   // 1
C
destructor 2
destructor 1
Run Code Online (Sandbox Code Playgroud)
  1. 为什么第一个析构函数被执行(但是没有为第二个对象执行)?

第二个析构函数在标记的第二个对象上执行// 1.这是A()第二次调用结束时临时的破坏push_back.

  1. 为什么在移动第一个对象之前执行第二个对象的移动?

注意:对我来说,第一个对象是复制的,而不是移动的,更多关于下面的内容.

答:异常安全.

说明:在此期间push_back,向量发现它具有完整缓冲区(一个)并且需要创建一个具有两个空间的新缓冲区.它创建了新的缓冲区.然后将第二个对象移动到该缓冲区中(在它的末尾).如果该构造抛出异常,则原始缓冲区仍然完好无损且vector保持不变.否则,将元素从第一缓冲区移动或复制到第二缓冲区(从而移动/复制第一个元素第二个).

如果A有一个noexcept移动构造函数,move则将使用它将旧缓冲区移动到新缓冲区.但是,如果移动构造函数不是noexcept那么copy将使用.这也是异常安全.如果从旧缓冲区到新缓冲区的移动可能失败,则旧缓冲区必须保持原样,以便vector可以恢复到其原始状态.

如果我添加noexcept到你的移动构造函数:

A( A && c) noexcept : j(c.j)
{
    std::cout<<"move "<<j<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)

然后我的输出是:

A
constructor 1
move 1
destructor 1
B
constructor 2
move 2
move 1
destructor 1  // 2
destructor 2
C
destructor 2
destructor 1
Run Code Online (Sandbox Code Playgroud)

请注意,标记的行// 2是在将旧元素移动构造到新缓冲区后从旧缓冲区中销毁第一个元素.

  1. 为什么最后两个析构函数被执行?

这标志着vector每个vector元素的破坏,从而破坏了每个元素.


seh*_*ehe 5

明智的使用reserve解决了你的问题的一半:http://ideone.com/5Lya6通过减少意外动作的数量(你没有明确要求)

另外不要忘记,在移动到矢量,temp的析构函数仍会触发.这就是为什么你必须确保即使在移动分配/构造之后,温度仍然保持在理智,可破坏的状态.

#include <iostream>
#include <utility>
#include <vector>

int i = 0;
struct A
{
    A() : j( ++i )
    {
        std::cout<<"constructor "<<j<<std::endl;
    }
    A( const A & c) : j(c.j)
    {
        std::cout<<"copy "<<j<<std::endl;
    }
    A( const A && c) : j(c.j)
    {
        std::cout<<"move "<<j<<std::endl;
    }
    ~A()
    {
        std::cout<<"destructor "<<j<<std::endl;
    }

    int j;
};

typedef std::vector< A > vec;

void foo( vec & v )
{
    v.push_back( std::move( A() ) );
}

int main()
{
    vec v;
    v.reserve(2);

    foo( v );
    foo( v );
}
Run Code Online (Sandbox Code Playgroud)