【问题标题】:C++: STL: vector: remove: destructor callsC++:STL:向量:删除:析构函数调用
【发布时间】:2012-02-12 07:29:39
【问题描述】:

代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct A {
  A(int i = -1): i_(i) {
    wcout << "Constructor: i = " << i_ << endl;
  }
  A(A const &a) {
    wcout << "Copy constructor: i = " << i_ << " a.i = " << a.i_ << endl;
    *this = a;
  }
  ~A() { wcout << "Destructor: i = " << i_ << endl; }
  A &operator=(A const& a) {
    wcout << "Copy assignment operator: i = " << i_ << " a.i = " << a.i_ << endl;
    i_ = a.i_;
    return *this;
  }
  bool operator==(A const& rhs) { return i_ == rhs.i_; }
  int get() { return i_; }
private:
  int i_;
};

int wmain() {
  A a[] = {1, 2, 3, 2, 4, 5};
  vector<A> v(a, a + sizeof a/sizeof a[0]);
  wcout << "==== Just before remove ====" << endl;
 remove(v.begin(), v.end(), 2);
  wcout << "==== Just after remove ====" << endl;

  return 0;
}

输出:

==== Just before remove ====
Constructor: i = 2
Destructor: i = 2
Constructor: i = 2
Destructor: i = 2
Constructor: i = 2
Destructor: i = 2
Copy assignment operator: i = 2 a.i = 3
Constructor: i = 2
Destructor: i = 2
Constructor: i = 2
Destructor: i = 2
Copy assignment operator: i = 3 a.i = 4
Constructor: i = 2
Destructor: i = 2
Copy assignment operator: i = 2 a.i = 5
==== Just after remove ====

问题是:为什么在 remove() 运行时调用了 6 次析构函数?我需要澄清这个烂摊子。

注意:请在您的系统上执行此代码,然后再回答。 注意:MSVCPP 11

【问题讨论】:

  • 您的输出与代码不匹配。数字从何而来?
  • 如果在析构函数中记录i_的值,就会很明显。提示:你的operator== 比较什么类型?

标签: c++ vector destructor std


【解决方案1】:

问题是:为什么在 remove() 被调用时析构函数被调用了 6 次 跑步吗?

总之,析构函数调用与2remove() 隐式转换为A 有关。每次这种隐式转换的结果超出范围时,都会调用A 的析构函数。

这些隐式转换的原因是remove() 需要将a 的每个元素与2 进行比较。唯一的方法是调用A::operator==(const A&amp;)

  bool operator==(A const& rhs) { ... }

由于rhs 的类型为const A&amp;,编译器:

  1. 调用A(int)2转换为A
  2. 致电operator==(const A&amp;)
  3. 调用A::~A() 来销毁临时文件。

后者是您看到的析构函数调用。

如果您将以下比较运算符添加到A,您会看到那些析构函数调用消失了:

  bool operator==(int rhs) { return i_ == rhs; }

或者,如果你像这样调用remove(),你会看到所有bar one的析构函数调用都消失了:

remove( v.begin(), v.end(), A(2) );

最后,如果您要创建A::A(int)explicit,编译器将不允许您使用2 作为最后一个参数调用remove()(您必须使用A(2) 调用它)。

【讨论】:

  • 确实 - 为了更清楚起见,OP 可以编写构造函数、复制构造函数和赋值,并制作这些日志,以查看创建了多少临时对象。
  • @DaddyM 现在写remove(v.begin(), v.end(), A(2)) 看看会发生什么。请记住,remove 是一个模板,它不是 take an A`,而是您提供的任何内容(在本例中为 int)。
  • 解释部分错误。 remove 将 2 作为 int 并为每个相等检查构造一个临时值。 std::remove 不应传递对象以按值进行比较。
  • @visitor:我在 7 分钟前编辑了答案来解决这个问题。我认为您可能正在查看旧的缓存版本。
  • +1 让精神力量真正理解问题(和正确答案)。
【解决方案2】:

问题是:为什么在 remove() 被调用时析构函数被调用了 6 次 跑步吗?

因为std::remove 只是重新排序元素,但不会从向量中删除任何内容。在重新排序的过程中,一些元素被复制构造。


接下来的代码会详细说明会发生什么:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct A {
  A(int i = -1): i_(i) {cout << "I was created" << i_ << endl;}
  ~A() {
    cout << "I was destroyed" << i_ << endl;
  }
  A(const A& o):i_(o.i_)
  {
    cout << "I was copied" << i_ << endl;
  }
  A& operator=(const A& o)
  {
    i_=o.i_;
    cout << "I was assigned" << i_ << endl;
    return *this;
  }
  int get() { return i_; }
  bool operator==(A const& rhs) { return i_ == rhs.i_; }
private:
  int i_;
};

int main() {
std::cout<<"start"<<std::endl;
  A a[] = {1, 2, 3, 2, 4, 5};

  std::cout<<"creating"<<std::endl;
  vector<A> v(a, a + sizeof a/sizeof a[0]);
  std::cout<<"removing"<<std::endl;
  remove( v.begin(), v.end(), 2 );
  std::cout<<"end"<<std::endl;
}

remove 将“已移除”的元素放在向量的末尾。

【讨论】:

  • 所有被破坏6次的元素都是“2”
【解决方案3】:

std::remove 被声明为

 template<typename ForwardIterator, typename Tp>
 ForwardIterator remove(ForwardIterator first, ForwardIterator last,
   const Tp& value);

在您的使用中,第三个参数 (2) 推导出为 int。因为int 变量不能直接与A 对象进行比较,所以首先必须为每个对象构造一个临时变量

if (*it == value) ...

比较结束时,临时对象被销毁。

(总而言之,你有一个隐藏的性能问题,这是由于有非显式的单参数,也就是转换构造函数。)

【讨论】:

    【解决方案4】:

    这真的很简单;你只需要了解std::remove 做了什么以及它是如何做的。提示:它确实从向量中删除元素。然后它会移动到您收藏的后面。在向量中移动元素涉及破坏原始元素。所以这就是你的析构函数调用的(部分)来源。

    另一部分来自临时对象 - 由于您将 int(而不是 struct A 的实例)作为最后一个参数传递给 std::remove,因此必须构造 A 的实例以用于比较。如果您想对您的代码强制执行更多规则,请尝试养成使用关键字explicit 作为单参数构造函数前缀的习惯。驱逐此类临时对象非常有效。然后您必须显式创建比较对象:

    remove(v.begin(), v.end(), A(2));
    

    【讨论】:

    • 实际上,std::remove 的行为未指定它在新结束后离开项目的状态。它们处于可破坏状态,但不能保证更多。在实践中,它使实现无需“移动”或“交换”它们,或者只是从它们复制并保持原样。
    • 我猜你一定是指 ISO 14882-2011 中的这一点“注意:范围 [ret,last) 中的每个元素,其中 ret 是返回值,具有有效但未指定的状态,因为算法可以消除元素......”。这只意味着这些对象不满足任何特定先决条件。这使得它们对于具有 no 前提条件的任何操作都完全有效,而不仅仅是破坏。此外,这无关紧要。最初在 ret 之前并且已被“删除”的对象也必须被销毁。这就是我的观点。
    • 在某种程度上,引用的结尾也很重要:因为算法可以通过交换或从最初在该范围内的元素移动来消除元素。未指定“移除”的元素是被销毁还是只是与“过去的新结束”元素交换;使用交换策略,根本不会调用析构函数。
    • 我会删除我的答案;你是对的 。基本上我们不知道算法是在做赋值、交换还是其他什么。它可能间接调用析构函数,但我们不确定
    猜你喜欢
    • 2011-03-23
    • 2012-03-13
    • 2016-07-11
    • 2014-05-27
    • 1970-01-01
    • 2021-04-10
    • 2015-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多