【问题标题】:vector and push_back()向量和 push_back()
【发布时间】:2020-11-20 00:50:53
【问题描述】:
#include <iostream>
#include <vector>
#include <algorithm>

class my {
  public:
    my() {
      counter++;
      std::cout << "class constructor" << counter << " \n";}

    ~my() {
      std::cout << "class destructor" << counter << " \n";
      counter--;
    }
  
    static inline int counter = 0;
};

int main()
{
    my v1;
    std::vector<my> my_vec;

    my * p = new my();
    my_vec.push_back(std::move(*p));
    my_vec.push_back(std::move(v1));
}

只是一个例子,但是我不明白我做错了什么,结果我得到了比我预期的更多的 2 个析构函数(预期 2)。谁能解释一下?

结果:

class constructor1
class constructor2
class destructor2
class destructor1
class destructor0
class destructor-1

【问题讨论】:

  • 移出的实例仍必须被销毁。
  • @underscore_d 但是第四个输出来自哪里?我数了 4 个实例,其中只有 3 个被销毁
  • 您也没有跟踪移动构造,因此计数器没有在那里修改,而是通过破坏相同的残余物而被修改。通过在构造时记住每个对象的计数器,而不是仅仅报告它,更容易看出事情似乎向南发展了。如果你愿意,一个“id”。它还将迫使您意识到移动对象应清除该 id 以注意它不再指代具体构造(但仍需要销毁)。
  • emplace_back 可能会给出与 push_back 不同的结果,因为它可能不会生成 push_back 将生成的副本。
  • 这是您的代码godbolt.org/z/ozxofc 的一个版本。我检测了复制构造函数并删除了p。一切看起来井井有条

标签: c++ vector push-back stdmove


【解决方案1】:

逐步分析程序:

my v1;

创建一个实例,调用构造函数。

my * p = new my();

另一个实例被创建,构造函数被调用。

my_vec.push_back(std::move(*p));

第二个实例的副本被插入到向量中;隐式定义的移动构造函数被调用(它只是复制;不打印输出)。

my_vec.push_back(std::move(v1));

向量为 2 个实例分配新存储,复制之前存储的实例到新存储中(调用隐式定义的移动构造函数,它只是进行复制,仍然没有输出),并且为旧存储中的实例调用析构函数(因此打印第一个析构函数输出)。

然后,向量超出范围,因此它的两个包含的元素被销毁(因此,2 个析构函数调用)。然后,v1 超出范围,打印第四个析构函数调用。实例p 已泄露,即从未销毁(内存泄漏)。

【讨论】:

    【解决方案2】:

    请看内联...

    #include <iostream>
    #include <vector>
    
    class my {
      public:
        my() {
          counter++;
          std::cout << "class constructor" << counter << " \n";
          std::cout << "Object Address : " << this << std::endl;
        }
    
    
        ~my() {
          std::cout << "class destructor" << counter << " \n";
          std::cout << "Object Address : " << this << std::endl;
          counter--;
        }
    
        static inline int counter = 0;
    };
    
    int main()
    {
        my v1;     /* 1. Object on stack (constructor called) */
        std::vector<my> my_vec;
    
        my * p = new my();   /* 2. Object created on heap (constructor called) */
        my_vec.push_back(std::move(*p));
        my_vec.push_back(std::move(v1));
    }
    

    输出是:

    class constructor 1 Address : 0x7ffee1488760
    class constructor 2 Address : 0x7f9f47400350
    class destructor 2 Address : 0x7f9f474026e0
    class destructor 1 Address : 0x7f9f474026f1
    class destructor 0 Address : 0x7f9f474026f0
    class destructor -1 Address : 0x7ffee1488760
    

    堆栈上的对象一旦超出范围即调用析构函数,即堆栈上对象的范围仅限于代码块。在这种情况下,当 main 退出时块结束。

    但是堆上的对象 (new) 是一个潜在的泄漏,如果它在 while 循环中会消耗内存并最终崩溃。对于每个new,您需要显式调用delete,这意味着您必须调用它的析构函数。感谢stack,它为我们做到了,但不是heap,而且c++也没有垃圾收集器。

    std::move 是一个复制操作,因此不会调用构造函数。详情here.

    【讨论】:

    • @Deore 谢谢。我看过 std::move 的文档,但实际上意味着魔法:“允许将资源从 t 有效转移到另一个对象”。我误解了 std::move,现在我了解更多了,但是您知道移动资源的“低级示例”吗?你能推荐一些东西吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-21
    • 2013-03-28
    • 2021-06-23
    • 2016-11-16
    • 2017-03-24
    • 2021-10-29
    相关资源
    最近更新 更多