C ++:STL:vector:remove:析构函数调用

时间:2012-01-17 13:38:45

标签: c++ vector destructor std

代码如下:

#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

4 个答案:

答案 0 :(得分:7)

  

问题是:为什么析构函数被调用6次而remove()是   运行

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

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

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

由于rhs的类型为const A&,因此编译器为:

  1. 调用A(int)2转换为A;
  2. 致电operator==(const A&);
  3. 调用A::~A()来销毁临时。
  4. 后者是您正在看到的析构函数调用。

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

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

    或者,如果您这样调用remove(),您将看到所有bar一个析构函数调用消失:

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

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

答案 1 :(得分:3)

  

问题是:为什么析构函数被调用6次而remove()是   运行

因为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将“已移除”元素放在向量的末尾。

答案 2 :(得分:2)

std::remove被声明为

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

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

构建一个临时变量
if (*it == value) ...

在比较结束时,临时被销毁。

(总而言之,你有一个隐藏的性能问题,因为有非显式的单参数,a.k.a。转换构造函数。)

答案 3 :(得分:1)

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

另一部分来自临时对象 - 因为您将int(而不是struct A的实例)作为最后一个参数传递给std::remove,所以A的实例必须是为了比较而建造的。如果你想对你的代码强加一些纪律,试着养成为一个参数构造函数添加关键字explicit的习惯。它在消除这种临时物体方面非常有效。然后,您必须明确创建比较对象:

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