Разрушение элемента контейнера std С++ и поведение вставки

Я сделал следующую маленькую программу: (в основном класс, который проверяет, создается ли он, копируется или уничтожается, и основной, который делает некоторые из них)

class Foo
{
public:
 Foo(string name): _name(name)
 {
  cout << "Instance " << _name << " of Foo created!" << std::endl;
 };
 Foo(const Foo& other): _name(other._name)
 {
  cout << "Instance " << _name << " of Foo copied!" << std::endl;
 };

 ~Foo()
 {
  cout << "Instance " << _name << " of Foo destroyed!" << std::endl;
 }
 string _name;
};



int main( int argc, char**argv)
{
 Foo albert("Albert");
 Foo bert("Bert");
 {
  vector<Foo> v1, v2;
  system("PAUSE");  

  v1.push_back(albert);
  system("PAUSE");

  v2.push_back(bert);
  system("PAUSE");

  v1 = v2;
  system("PAUSE");  
 }
  system("PAUSE");
}

Вывод выглядит следующим образом:

Instance Albert of class Foo created!
Instance Bert of class Foo created!
Press any key...
Instance Albert of class Foo copied!    
Instance Albert of class Foo copied!     // why another copy?
Instance Albert of class Foo destroyed!  // and destruction?
Press any key...
Instance Bert of class Foo copied!
Instance Bert of class Foo copied!
Instance Bert of class Foo destroyed!
Press any key...                      // v1=v2 why did the albert instance not get destroyed?
Press any key...                       
Instance Bert of class A destroyed!
Instance Bert of class A destroyed!
Press any key...                       // there's still an albert living in the void

Мне это кажется очень странным. Зачем мне вообще передавать что-то в качестве ссылки, если оно все равно скопировано дважды? Почему v1.operator=(other) не уничтожает содержащиеся в нем элементы? Это хорошо сочетается с поведением shared_ptr. Может кто-нибудь сказать мне, почему?

ДОПОЛНЕНИЕ Я поставил это в бесконечный цикл и проверил использование памяти, по крайней мере, похоже, что это не приводит к утечке памяти.

ДОПОЛНЕНИЕ Хорошо, мем не проблема, потому что он использует оператор = вместо копирования ctor, хорошо, спасибо. Когда я добавляю

v1.reserve(10);
v2.reserve(10);

имеет место логическое количество копий. без этого он перераспределяет и копирует весь вектор для каждого push_back (что я считаю довольно замедленным даже для небольших векторов). Глядя на это, я подумаю об использовании .reserve и оптимизирую свои операторы присваивания, черт возьми :)

ДОПОЛНЕНИЕ: ОБЗОР

  1. Все эти проблемы кажутся характерными для VC++2005.
  2. Если размеры двух контейнеров совпадают, моя реализация использует оператор = для элементов вместо уничтожения старых и копирования новых, что кажется разумной практикой. ЕСЛИ размеры различаются, используется обычное уничтожение и копирование.
  3. С реализацией 2005 года приходится использовать резерв! В противном случае ужасная и не соответствующая стандарту производительность.
  4. Эти черные ящики намного чернее, чем я думал.

person AndreasT    schedule 07.09.2009    source источник
comment
Вы пытались скомпилировать это как релизную сборку?   -  person jalf    schedule 07.09.2009
comment
Это легко попробовать самостоятельно, просто скопируйте и вставьте в пустой проект, добавьте iostream, vector и string и вперед.   -  person AndreasT    schedule 07.09.2009
comment
Да, и релиз, и отладка дают одинаковый результат.   -  person AndreasT    schedule 07.09.2009
comment
этот вывод не может соответствовать коду, что за вещь класса А ....?   -  person Pieter    schedule 07.09.2009
comment
@Pieter: я очистил программу для публикации, но скопировал старый вывод. Я исправил это. Однако результат тот же.   -  person AndreasT    schedule 07.09.2009
comment
Я никогда не использую VC++, поэтому не могу проверить это сам, но я был бы очень удивлен, если бы 3. С реализацией 2005 года приходилось использовать резерв! В противном случае ужасная и не соответствующая стандарту производительность. было бы правдой, есть что-нибудь, чтобы поддержать это?   -  person Pieter    schedule 07.09.2009


Ответы (4)


Зачем мне вообще передавать что-то в качестве ссылки, если оно все равно копируется дважды?

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

Возможно, реализация вашего компилятора push_back() использует временную дополнительную копию. На моей машине (gcc в Mac OS X) во время push_back() нет дополнительных копий (согласно выводу вашей программы).

Эта копия происходит где-то в коде STL, а не в вашем конструкторе копирования (поскольку он использует ссылку).

Почему v1.operator=(other) не уничтожает содержащиеся в нем элементы?

Foo::operator= будет вызываться для экземпляра "albert" с экземпляром "bert" в качестве аргумента. Следовательно, здесь нет неявной операции уничтожения и копирования. Вы можете проверить это, предоставив собственную реализацию оператора:

Foo& operator=(const Foo& other) {
    cout << "Instance " << other._name << " of Foo assigned to " << _name << "!" << std::endl;
    return *this;
}

Это производит следующий вывод на моей машине:

Экземпляр Albert of Foo создан!
Экземпляр Bert of Foo создан!
Экземпляр Albert of Foo скопирован!
Экземпляр Bert of Foo скопирован!
Экземпляр Bert of Foo присвоен Albert!
Экземпляр Bert of Фу уничтожен!
Экземпляр Альберт из Фу уничтожен!
Экземпляр Берт из Фу уничтожен!
Экземпляр Альберт из Фу уничтожен!

person Ferdinand Beyer    schedule 07.09.2009
comment
И имейте в виду, что простое отслеживание копий означает, что ваш копировщик становится более сложным до такой степени, что оптимизатор больше не может его устранить. Итак, вы считаете вызовы ctor, которые существуют только потому, что они учитываются! - person MSalters; 07.09.2009
comment
Аргумент черного ящика действителен, однако я всегда надеялся, что это не глупые черные ящики :). ‹субъективно!›Очередное разочарование M$.‹/субъективно› - person AndreasT; 07.09.2009
comment
@MSalters: Это интересно. Принцип неопределенности C++. - person stribika; 07.09.2009
comment
Что ж, у них могут быть свои причины. На самом деле, вы никогда не должны хранить дорогостоящие объекты в контейнерах STL, кроме указателей или интеллектуальных указателей. Копирование указателей дешево. (Кстати: вы можете захотеть изменить первый аргумент вашего ctor на const std::string& вместо std::string. Это экономит одну дополнительную копию для каждого экземпляра объекта.) - person Ferdinand Beyer; 07.09.2009
comment
Дорогой экземпляр или нет, перераспределение само по себе дорого. Ну, я знаю о const refs, просто в примере это не имеет значения, поэтому я поленился ;-) - person AndreasT; 07.09.2009

Есть автоматически сгенерированный оператор =. Когда вы делаете v1 = v2, используется этот оператор. В этот момент один из экземпляров «albert» становится «bert». Попробуйте добавить эту функцию в Foo:

Foo& operator = (const Foo& rval) {
    cout << _name << " = " << rval._name << endl;
    _name = rval._name;
    return *this;
}

Это то же самое, что и автоматически сгенерированное, но выводит отладочное сообщение, чтобы вы могли видеть, что происходит.

person stribika    schedule 07.09.2009
comment
ну а как же лишние копии и утечка мемов (чего на самом деле не происходит, см. выше) - person AndreasT; 07.09.2009
comment
Если вы добавите эту функцию (или статический счетчик), вы увидите, что утечки нет. Что касается дополнительных копий, нет гарантии, что контейнер STL скопирует что-то только один раз. Это может быть передача по значению или что-то еще в зависимости от вашей реализации STL. - person stribika; 07.09.2009

«Двойное копирование» не происходит при компиляции с помощью GCC. Это должно быть связано с тем, как std::vector реализован в VC++.

person Vijay Mathew    schedule 07.09.2009

Visual Studio 2008 дает мне следующий вывод:

Instance Albert of Foo created!
Instance Bert of Foo created!
Press any key to continue . . .
Instance Albert of Foo copied!
Press any key to continue . . .
Instance Bert of Foo copied!
Press any key to continue . . .
Press any key to continue . . .  << here auto-generated operator= doing its job
Instance Bert of Foo destroyed!
Instance Bert of Foo destroyed!  << this is Albert was originally 
Press any key to continue . . .

Похоже, что реализация std::vector не очень эффективна в VS2005.

person Kirill V. Lyadvinsky    schedule 07.09.2009