Безопасно ли хранить объекты класса, который имеет std :: auto_ptr в качестве переменной-члена в std :: vector? - PullRequest
3 голосов
/ 31 марта 2009

Я не могу использовать shared_ptr в моем проекте, без повышения: (

Итак, у меня есть класс, примерно похожий на класс ниже:

class MyClass
{
private:
  std::auto_ptr<MyOtherClass> obj;
};

Теперь я хочу сохранить экземпляры вышеуказанного класса в std :: vector. Это безопасно? Я прочитал здесь , что неправильно использовать std :: auto_ptr с контейнерами STL. Относится ли это к моей ситуации здесь?

Ответы [ 8 ]

6 голосов
/ 31 марта 2009

Это небезопасно, потому что, когда контейнер будет копировать MyClass, оператор копирования по умолчанию для экземпляра будет вызывать функцию копирования для всех членов - и для члена auto_ptr, и у нас будет такая же ситуация, как вы описали в своем вопросе (хранение auto_ptr в контейнере) *

Кстати: во избежание путаницы во время компиляции добавьте

private:
  MyClass& operator=( const MyClass& );  
  MyClass( const MyClass& );

Ошибка вывода компилятора, если вы попытаетесь использовать операторы копирования, это может спасти вас от часов отладки.

3 голосов
/ 31 марта 2009

не допустимо иметь объект, который содержит auto_ptr в стандартном контейнере. Вы сталкиваетесь с неопределенным поведением. Две общие проблемы:

  • std :: vector <> :: resize копирует свой аргумент в каждый созданный элемент. Первая копия будет «успешной» (см. Ниже, почему нет), но каждая дальнейшая копия будет пустой, потому что скопированный элемент также пуст!
  • Если что-то происходит во время перераспределения, вы можете скопировать некоторые элементы (в новый буфер) - но отбрасываемую копию - и другие элементы нет, потому что push_back не должно иметь никаких эффектов, если происходит исключение выброшены. Таким образом, некоторые из ваших элементов теперь пусты.

Поскольку это все о неопределенном поведении, это не имеет большого значения. Но даже , если мы попытаемся придумать такое поведение на основе того, что мы считаем действительным, мы все равно потерпим неудачу. Все функции-члены, такие как push_back, resize и т. Д., Имеют константную ссылку, которая принимает объект типа T. Таким образом, ссылка типа T const& пытается скопироваться в элементы вектора. Но неявно созданный конструктор копирования / оператор присваивания выглядит как T(T&), то есть для копирования требуется объект non-const ! Хорошие реализации Стандартной библиотеки проверяют это и не компилируют при необходимости.

До следующей версии C ++ вам придется с этим жить. Следующий будет поддерживать типы элементов, которые просто подвижные . То есть перемещенный объект не обязательно должен быть равен перемещаемому объекту. Это позволит помещать в контейнеры потоков , указателей передачи права собственности и потоков .

Посмотрите, что стандарт говорит для этого (17.4.3.6):

В некоторых случаях (функции замены, функции-обработчики, операции над типами, используемые для создания экземпляров стандартных шаблонных компонентов библиотеки), стандартная библиотека C ++ зависит от компонентов, предоставляемых программой C ++. Если эти компоненты не соответствуют их требованиям, Стандарт не устанавливает требований к реализации.

В частности, эффекты не определены в следующих случаях:

  • для типов, используемых в качестве аргументов шаблона при создании экземпляра компонента шаблона, если операции над типом не реализуют семантику применимого подпункта Требования (20.1.5, 23.1, 24.1, 26.1).
3 голосов
/ 31 марта 2009

Как сказал Нил Баттерворт, auto_ptr, вероятно, не тот путь.

boost :: shared_ptr ясно, но вы говорите, что не можете использовать boost.

Позвольте мне упомянуть, что вы можете скачать boost, извлечь то, что вам нужно для shared \ ptr, используя bcp tool и использовать boost :: shared_ptr . Это будет означать только несколько добавленных файлов hpp в вашем проекте. Я считаю, что это правильный путь.

2 голосов
/ 31 марта 2009

Я отправил вопрос в качестве продолжения к этому ответу смотрите Класс, содержащий auto_ptr, хранящийся в векторе .

Если предположить, что в вашем классе нет пользовательского конструктора копирования, то нет, вероятно (см. Ниже) небезопасно. Когда ваш класс копируется (как это будет происходить при добавлении в вектор), будет использован конструктор копирования auto_ptr. Это странное поведение при передаче права собственности на объект, который копируется в копию, и, таким образом, указатель копируемого объекта теперь равен нулю.

Возможно, хотя и маловероятно, что вы действительно хотите такое поведение, и в этом случае auto_ptr безопасен. Если вы этого не сделаете, вы должны либо:

  • добавить конструктор копирования для управления копированием

Обратите внимание, что этого недостаточно - см. Дополнительный вопрос, упомянутый выше, для получения дополнительной информации.

или

  • использовать более умный указатель с подсчетом ссылок, например, один из интеллектуальных указателей наддува
1 голос
/ 01 апреля 2009

Если вы хотите использовать класс, который использует auto_ptr в контейнере, вы можете просто предоставить конструктор копирования и оператор присваивания самостоятельно:

class MyClass
{
private:
  const std::auto_ptr<MyOtherClass> obj; // Note const here to keep the pointer from being modified.

public:
  MyClass(const MyClass &other) : obj(new MyOtherClass(*other.obj)) {}
  MyClass &operator=(const MyClass &other)
  {
      *obj = *other.obj;
      return *this;
  }
};

Но, как уже упоминалось, стандарт позволяет контейнерам делать копии и присваивать и предполагает, что содержащиеся классы будут вести себя особым образом, который нарушает auto_ptr. Определив методы, описанные выше, вы можете создать класс, содержащий поведение auto_ptr. Даже если ваша реализация отлично работает с auto_ptrs, вы рискуете обнаружить, что другая реализация не работает. Стандарт только дает гарантии производительности и наблюдаемого поведения, а не реализации.

1 голос
/ 31 марта 2009

Причина, по которой небезопасно создавать экземпляр вектора auto_pointer, заключается в том, что существует алгоритм: sort (), который делает копию одного объекта в вашем контейнере в стеке. (sort () реализует быструю сортировку, которая нуждается в "pivot") И поэтому удаляем его при выходе из scpope функции sort (). Также любой алгоритм или ваша собственная функция, которая может принять ваш контейнер в качестве параметра и скопировать один из его объектов в стек, приведет к этой проблеме.

Что ж, в вашем случае просто, вы должны убедиться, что ваш класс не ведет себя как auto_ptr, или гарантировать, что вы никогда не будете вызывать такую ​​функцию / алгоритм, который может удалить ваши базовые объекты. Первое решение лучше всего, по мне:)

Таким образом, ваш конструктор копирования и ваш оператор воздействия также не должны отдавать свойство объекта-указателя.

Лучший способ добиться этого - обернуть интеллектуальный указатель надстройки вместо auto_ptr, чтобы сделать ваш контейнер безопасным при вызове такой функции / алгоритма.

Кстати, по моему мнению, определение лучшего конструктора копирования / оператора аффектации для обхода этой проблемы не является хорошим решением: я не вижу хорошей реализации конструктора копирования (а также оператора аффектации), которая могла бы защитить результат применения алгоритма sort ().

1 голос
/ 31 марта 2009

Копирование объекта MyClass вызовет либо оператор присваивания, либо конструктор копирования. Если они не перегружены для обработки auto_ptr <> необычным способом, они будут передавать вызов для копирования конструктора (или оператора присваивания) члену auto_ptr <>. Это может привести к проблемам, описанным в вопросе, который вы связали.

0 голосов
/ 31 марта 2009

Это не будет работать. auto_ptr не считает ссылки, что означает, что при первом вызове деструктора ваш указатель будет освобожден.

Вместо этого используйте boost :: shared_ptr.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...