将临时对象移动到矢量中

时间:2011-10-06 08:12:31

标签: c++ c++11 object-lifetime

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

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

constructor 1
move 1
destructor 1
constructor 2
move 2
move 1
destructor 1
destructor 2
destructor 1
destructor 2

问题:

  1. 为什么第一个析构函数被执行(但是没有为第二个对象执行)?
  2. 为什么移动第一个物体之前执行的第二个物体的移动?
  3. 为什么最后两个析构函数被执行了?
  4. PS我刚检查过,对象确实按预期放置(第一个进入向量中的位置0,第二个进入向量中的位置1)

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

    g++ n1.cpp -Wall -Wextra -pedantic -ansi -std=c++0x -O3
    

4 个答案:

答案 0 :(得分:9)

我稍微记下了你的例子:

#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";
}
  1. 我已从移动构造函数中删除了const
  2. 我已从std::move删除push_back(这是多余的)。
  3. 我在foo的调用之间插入了标记。
  4. 对我来说,这打印出类似于你的代码:

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

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

      
        
    1. 为什么移动第一个物体,在移动第一个物体之前执行   对象
    2.   

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

    答案:异常安全。

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

    如果Anoexcept移动构造函数,则会使用move将其从旧缓冲区移动到新缓冲区。但是,如果移动构造函数不是noexcept,则将使用copy。这也是异常安全。如果从旧缓冲区到新缓冲区的移动可能失败,则旧缓冲区必须保持不变,以便vector可以恢复到其原始状态。

    如果我将noexcept添加到您的移动构造函数中:

    A( A && c) noexcept : j(c.j)
    {
        std::cout<<"move "<<j<<std::endl;
    }
    

    然后我的输出是:

    A
    constructor 1
    move 1
    destructor 1
    B
    constructor 2
    move 2
    move 1
    destructor 1  // 2
    destructor 2
    C
    destructor 2
    destructor 1
    

    请注意,标记为// 2的行是旧的缓冲区中第一个元素被移植到新缓冲区后的破坏。

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

    这标志着vector的破坏以及每个vector元素的破坏。

答案 1 :(得分:5)

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

另外请不要忘记,在移动到向量中之后,temp的析构函数仍会触发。这就是为什么你必须确保即使在移动赋值/构造之后,temp仍然保持在 sane ,可破坏的状态。

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

答案 2 :(得分:4)

向量正在增加其容量并在调用push_back期间移动内部元素。

答案 3 :(得分:2)

移动构造函数不会“销毁”移动的对象。

#include <iostream>

struct Foo { 
  int i;
  bool active;

  Foo(int i): i(i), active(true) {}
  Foo(Foo&& rhs): i(rhs.i), active(rhs.active) { rhs.active = false; }
  Foo(Foo const& rhs): i(rhs.i), active(rhs.active) {}
  ~Foo() { std::cout << i << (active ? " active": " inactive") << "\n"; }
};


int main() {
  Foo foo;
  Bar bar(std::move(foo));
}

output给出:

1 active
1 inactive