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( 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. 为什么最后每个对象执行两个析构函数?

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

PPS 如果重要的话,我使用的是 gcc 4.3,我像这样编译程序:

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

4 回答 4

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";
}
  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
  1. 为什么执行第一个析构函数(但没有为第二个对象执行)?

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

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

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

答:异常安全。

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

如果A有一个noexcept移动构造函数 amove将用于将其从旧缓冲区移动到新缓冲区。但是,如果移动构造函数不是noexcept,则将copy使用 a 。这又是为了异常安全。如果从旧缓冲区到新缓冲区的移动失败,那么旧缓冲区必须保持原样,以便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. 为什么最后每个对象执行两个析构函数?

这标志着 ' 的破坏,vector从而标志着每个vector' 元素的破坏。

于 2011-10-06T13:08:46.457 回答
5

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

也不要忘记临时的析构函数在被移动到向量后仍然会触发。这就是为什么即使在移动分配/构造之后,您也必须确保温度保持在正常、可破坏的状态。

#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 );
}
于 2011-10-06T08:23:45.750 回答
4

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

于 2011-10-06T08:18:46.043 回答
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));
}

输出给出:

1 active
1 inactive
于 2011-10-06T08:24:59.383 回答