2012-01-17 1 views
2

код выглядит следующим образом:C++: STL: вектор: удалить: деструктор вызывает

#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 раз, а удалить() был запущен ? Мне нужно, чтобы этот беспорядок был прояснен.

Примечание: выполнить этот код на вашей системе, пожалуйста, прежде чем ответить. Примечание: MSVCPP 11

+0

Ваш вывод не соответствует коду. Откуда берутся цифры? – thiton

+3

Если вы зарегистрируете значение 'i_' в деструкторе, это будет очевидно. Подсказка: какие типы ваш 'operator ==' сравнивают? –

ответ

7

вопрос: почему деструктор вызывается в 6 раз, а удалить() был работает?

В итоге, деструктор вызовы должны делать с 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(), как это так, вы будете видеть весь бар один Деструктор вызовы исчезают:

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

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

+0

Действительно - для большей ясности OP мог написать конструктор, конструктор и назначение копии, а также сделать этот журнал, чтобы увидеть, сколько временных времен создается. –

+1

@DaddyM Теперь напишите 'remove (v.begin(), v.end(), A (2))' и посмотрите, что произойдет. Просто помните, что 'remove' - это шаблон, который не' принимает 'A', а все, что вы ему даете (в этом случае' int'). –

+2

Объяснение частично неверно. 'remove' принимает 2 как' int' и создает временную для каждой проверки равенства. 'std :: remove' не должен передавать объект для сравнения по сравнению со значением по значению. – visitor

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 является размещение «удаленные» элементы в конце вектора.

+1

все элементы, разрушенные 6 раз, равны «2» – DaddyM

1

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

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

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

На самом деле поведение 'std :: remove' не указано в отношении состояния, в котором оно покидает элементы за новым концом. Они находятся в разрушительном состоянии, но больше не гарантировано. На практике он оставляет реализацию свободной от «перемещения» или «замены» или просто копирует их и оставляет их как есть. –

+0

Я думаю, вы должны ссылаться на этот пункт в ИСО 14882-2011 «Примечание: каждый элемент в диапазоне [ret, last), где ret - это возвращаемое значение, имеет действительное, но неопределенное состояние, потому что алгоритмы могут исключать элементы. .. ". Это означает, что эти объекты не удовлетворяют никаким * специальным * предварительным условиям. Это делает их совершенно допустимыми для любых операций с предварительными условиями * no *, а не просто с уничтожением. Кроме того, это не имеет значения. Объекты, которые были первоначально до ret и которые были «удалены», также должны были быть уничтожены. Это была моя точка зрения. – bronekk

+0

В частности, важно также отметить конец цитаты: * поскольку алгоритмы могут исключать элементы путем ** замены с ** или перемещения из элементов, которые первоначально находились в этом диапазоне *. Не указано, уничтожены ли «удаленные» элементы или просто обменены с элементами «прошлого-нового конца»; с помощью стратегии свопинга никакой деструктор не вызывается вообще. –

2

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

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

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

if (*it == value) ... 

В конце сравнения, временный разрушается.

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