C ++: STL: vector: remove: вызовы деструктора - PullRequest
2 голосов
/ 17 января 2012

Код следующий:

#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 ====

Вопрос в том, почему деструктор вызывается 6 раз, когда remove () работала?Мне нужно разобраться в этом беспорядке.

ПРИМЕЧАНИЕ: перед тем, как ответить, выполните этот код в своей системе.ПРИМЕЧАНИЕ: MSVCPP 11

Ответы [ 4 ]

7 голосов
/ 17 января 2012

Вопрос в том, почему деструктор вызывается 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() для уничтожения временных.

Последние являютсявызовы деструктора, которые вы видите.

Если вы добавите следующий оператор сравнения к A, вы увидите, что эти вызовы деструктора исчезнут:

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

В качестве альтернативы, если выесли бы вы вызывали remove() примерно так, вы увидите, что все вызовы деструктора bar one исчезают:

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

Наконец, если вы сделаете A::A(int) explicit, компилятор не позволит вамвызовите remove() с 2 в качестве последнего аргумента (вам нужно будет вызвать его с A(2)).

3 голосов
/ 17 января 2012

Вопрос в том, почему деструктор вызывается 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 голосов
/ 17 января 2012

std::remove объявлен как

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

В вашем случае третий аргумент (2) выводится как int.Поскольку переменная int не является напрямую сопоставимой с объектом A, сначала необходимо создать временное значение для каждого

if (*it == value) ...

. В конце сравнения временное уничтожается.

(В общем, у вас скрытая проблема с производительностью, связанная с наличием неявного конструктора преобразования с одним аргументом.)

1 голос
/ 17 января 2012

Ну, это довольно просто; вам нужно только понять, что делает std::remove и как это делает. Подсказка: он не удаляет элементы из вектора. Вместо этого он перемещается в конец вашей коллекции. Перемещение элемента в векторе подразумевает уничтожение исходного элемента. Так вот откуда (часть) ваши деструкторные вызовы исходят.

Другая часть исходит от временных объектов - поскольку вы передали int (а не экземпляр struct A) в качестве последнего параметра std::remove, экземпляр A должен быть создан с целью сравнение. Если вы хотите, чтобы ваш код был немного более дисциплинированным, попробуйте сделать привычкой добавлять префикс однопараметрических конструкторов к ключевому слову explicit. Очень эффективно изгонять такие временные объекты. Затем вам придется явно создать объект сравнения:

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