将临时对象移动到向量中

发布于 2024-12-08 13:47:58 字数 1166 浏览 0 评论 0原文

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

The example above produces next output :

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

Questions :

  1. Why is the 1st destructor executed (but it isn't executed for the 2nd object)?
  2. Why is move of the 2nd object, executed before the move of the 1st object?
  3. Why are at the end two destructors for each object executed?

PS I just checked, and the objects are indeed placed as expected (the 1st goes to the position 0 in the vector, and the 2nd goes to the position 1 in the vector)

PPS If it matters, I am using gcc 4.3, and I compile the program like this :

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

被你宠の有点坏 2024-12-15 13:47:58

我稍微重新编码了您的示例:

#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. 我已经从 push_back 中删除了 std::move (它是多余的)。
  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. 为什么第一个析构函数被执行(但它没有被执行)
    第二个对象)?

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

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

注意:对我来说,第一个对象是复制的,而不是移动的,更多信息如下。

答:异常安全。

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

如果 A 有一个 noexcept 移动构造函数,则将使用 move 将其从旧缓冲区移动到新缓冲区。但是,如果移动构造函数不是 noexcept,则将使用 copy。这又是为了异常安全。如果从旧缓冲区到新缓冲区的移动可能失败,则必须保持旧缓冲区完好无损,以便向量可以恢复到其原始状态。

如果我将 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 的每个元素的破坏。

I've slightly recoded your example:

#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. I've removed the const from the move constructor.
  2. I've removed the std::move from the push_back (it is superfluous).
  3. I've inserted markers between the calls to foo.

For me this prints out similar to your code:

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. Why is the 1st destructor executed (but it isn't executed for the
    2nd object)?

The 2nd destructor is executed for the 2nd object at the line marked // 1. This is the destruction of the temporary A() at the end of the second call to push_back.

  1. Why is move of the 2nd object, executed before the move of the 1st
    object?

Note: For me the 1st object is copied, not moved, more about that below.

Answer: Exception safety.

Explanation: During this push_back the vector discovers it has a full buffer (of one) and needs to create a new buffer with room for two. It creates the new buffer. Then moves the second object into that buffer (at the end of it). If that construction throws an exception, the original buffer is still intact and the vector remains unchanged. Otherwise the elements are moved or copied from the first buffer to the second buffer (thus move/copying the first element second).

If A has a noexcept move constructor a move will be used to move it from the old buffer to the new. However if the move constructor is not noexcept then a copy will be used. This is again for exception safety. If the movement from the old buffer to the new can fail, then the old buffer must be left intact so that the vector can be restored to its original state.

If I add noexcept to your move constructor:

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

Then my output is:

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

Note the line marked // 2 is the destruction of the first element from the old buffer, after it has been move constructed into the new buffer.

  1. Why are at the end two destructors for each object executed?

This is marking the destruction of the vector and thus the destruction of each of the vector's elements.

暮色兮凉城 2024-12-15 13:47:58

明智地使用 reserve 可以解决一半的问题:http://ideone.com/5Lya6< /a> 通过减少意外移动的数量(您没有明确请求)。

另外,不要忘记,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 );
}

Judicious use of reserve solves half your problem: http://ideone.com/5Lya6 by reducing the number of unexpected moves (that you don't explicitely request)

Also don't forget that the temp's destructor will still fire after having being moved into the vector. This is why you have to make sure that the temp remains in sane, destructible state even after the move assignment/construction.

#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 );
}
深海夜未眠 2024-12-15 13:47:58

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

The vector is increasing its capacity and moving the internal elements during the call to push_back.

溺孤伤于心 2024-12-15 13:47:58

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

#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

A move constructor does not "destroy" the moved object.

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

The output gives:

1 active
1 inactive
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文