Контейнеры 'auto_ptr' и STL: написание примера ошибочного использования - PullRequest
1 голос
/ 25 декабря 2011

Этот вопрос возник после прочтения этого урока: http://www.cprogramming.com/tutorial/auto_ptr.html

Там вы можете найти следующее утверждение: Тонким следствием этого поведения является то, что auto _ ptrs don 'не работают во всех сценариях.Например, использование объектов auto _ ptr со стандартной библиотекой шаблонов может привести к проблемам, так как некоторые функции в STL могут создавать копии объектов в контейнерах, таких как класс контейнера вектора.Одним из примеров является функция сортировки, которая делает копии некоторых объектов в контейнере, которые сортируются.Как следствие, эта копия может безболезненно удалить данные в контейнере!

Большинство статей, касающихся auto_ptr, говорят нам что-то вроде следующего: «Никогда не используйте auto_ptr с контейнерами STL!копировать их элементы при выполнении внутренних операций. Например, рассмотрим sort для std::vector ".

Поэтому моя цель написать пример кода, который иллюстрирует этот момент, или доказать, что такие примеры только теоретическиправда и странно на практике .

PS @everybody_who_also_knows_that_auto_ptr_is_deprecated Я также знаю об этом.Но разве вы не учитываете технические причины (старый код или старый компилятор), которые не позволяют использовать новые контейнеры указателей?И более того, этот вопрос о старом и плохом (если хотите) auto_ptr.

Ответы [ 5 ]

4 голосов
/ 25 декабря 2011

У меня сейчас нет MSVC, но, судя по ошибке из g ++, я думаю, что причина в этом:

auto_ptr<T> имеет только «конструктор копирования», который принимает изменяемые ссылки (§D.10.1.1 [auto.ptr.cons] / 2–6):

auto_ptr(auto_ptr& a) throw();
template<class Y> auto_ptr(auto_ptr<Y>& a) throw();

Но vector::push_back примет константную ссылку (§23.3.6.1 [vector.overview] / 2).

void push_back(const T& x);

Так что невозможно создать auto_ptr с помощью push_back, потому что ни один конструктор не принимает константную ссылку.

0 голосов
/ 11 июля 2016

Из того, что вы пишете, кажется, что вы уже знаете все, что нужно знать о контейнерах auto_ptr s и почему они небезопасны.

Поэтому я предполагаю, что ваш интерес к контейнерам auto_ptr s чисто ориентирован на преподавание. Я понимаю ваше разочарование в попытке создать преднамеренный контрпример: фактически, большинство разработчиков стандартных контейнеров используют обходные пути, чтобы избежать случайного запуска нарушенной семантики auto_ptr s.

Итак, вот пример, который я написал сам для обучения:

class MyClass {
  int a;
public:
  MyClass (int i) : a(i) {  }
  int get() const { return a; }
};

int main() {
  constexpr unsigned size = 10;
  std::vector< std::auto_ptr<MyClass> > coap;
  coap.resize(size);

  for (unsigned u=0; u<size; u++)
    coap[u] = std::auto_ptr<MyClass>( new MyClass( rand() % 50 ));

  std::sort( coap.begin(), coap.end(),
           []( std::auto_ptr<MyClass> a,
               std::auto_ptr<MyClass> b) { return a->get() < b->get(); }); 
}

Компиляция с помощью g ++ 4.9.2 приведет к исполняемому файлу, который будет прекрасно работать с segfault.

Вы можете переписать приведенный выше пример еще более кратко, используя вывод типа:

  std::sort( coap.begin(), coap.end(),
           []( auto a, auto b) { return a->get() < b->get(); }); 

Обратите внимание, что проблема не в конкретной реализации std::sort, которая, по-видимому, auto_ptr -безопасна. Скорее, в лямбда-функции сравнения, которую я передаю std::sort, она сознательно принимает свои аргументы по значению, таким образом уничтожая объекты в контейнере каждый раз, когда выполняется сравнение.

Если вы изменили лямбду так, чтобы она получала свои аргументы по ссылке, как показано ниже, большинство реализаций STL будут вести себя правильно, даже если вы делаете что-то, что концептуально неправильно.

  std::sort( coap.begin(), coap.end(),
           []( const std::auto_ptr<MyClass> & a,
               const std::auto_ptr<MyClass> & b) { return a->get() < b->get(); }); 

Удачи!

0 голосов
/ 26 декабря 2011

ШАГ 1 Давайте решим эту проблему прямым путем:

#include <iostream>
#include <vector>
#include <algorithm>

template<> struct std::less<std::auto_ptr<int>>: public std::binary_function<std::auto_ptr<int>, std::auto_ptr<int>, bool> {
  bool operator()(const std::auto_ptr<int>& _Left, const std::auto_ptr<int>& _Right) const
  { // apply operator< to operands
    return *_Left < *_Right;
  }
};

int wmain() {
  using namespace std;

  auto_ptr<int> apai(new int(1)), apai2(new int(2)), apai3(new int(3));
  vector<auto_ptr<int>> vec;
  vec.push_back(apai3);
  vec.push_back(apai);
  vec.push_back(apai2);

  for ( vector<auto_ptr<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
    wcout << i->get() << L'\t';

  vector<int> vec2;
  vec2.push_back(3);
  vec2.push_back(2);
  vec2.push_back(5);

  sort(vec2.begin(), vec2.end(), less<int>());

  sort(vec.begin(), vec.end(), less<auto_ptr<int>>());

  return 0;
}

На MSVCPP11 текст ошибки следующий: _ Ошибка 1 ошибка C2558:класс 'std :: auto _ptr <<em> Ty>': конструктор копирования недоступен или конструктор копирования объявлен как «явный» c: \ program files (x86) \ microsoft visual studio 11.0 \ vc \ include \ xmemory0 608

Вывод таков: я даже не могу скомпилировать такой пример.Почему они мешают мне делать то, что я не могу скомпилировать?Их предупреждения не всегда верны.


ШАГ 2

Мы не можем использовать auto_ptr в качестве vector типа элемента напрямую из-за дизайна auto_ptr.Но мы можем обернуть `auto_ptr 'способом, представленным ниже.

#include <iostream>
#include <vector>
#include <algorithm>

template<typename T> class auto_ptr_my: public std::auto_ptr<T> {
public:
  explicit auto_ptr_my(T *ptr = 0) {
    this->reset(ptr);
  }
  auto_ptr_my<T> &operator=(const auto_ptr_my<T> &right) {
    *(static_cast<auto_ptr<T> *>(this)) = *(static_cast<auto_ptr<T> *>(const_cast<auto_ptr_my *>(&right)));
    return *this;
  }
  auto_ptr_my(const auto_ptr_my<T>& right) {
    *this = right;
  }
};

template<> struct std::less<auto_ptr_my<int>>: public std::binary_function<auto_ptr_my<int>, auto_ptr_my<int>, bool> {
  bool operator()(const auto_ptr_my<int>& _Left, const auto_ptr_my<int>& _Right) const
  { // apply operator< to operands
    return *_Left < *_Right;
  }
};

int wmain() {
  using namespace std;

  auto_ptr_my<int> apai(new int(1)), apai2(new int(2)), apai3(new int(3));

  vector<auto_ptr_my<int>> vec;
  vec.push_back(apai3);
  vec.push_back(apai);
  vec.push_back(apai2);

  for ( vector<auto_ptr_my<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
    wcout << **i << L'\t';

  sort(vec.begin(), vec.end(), less<auto_ptr_my<int>>());

  for ( vector<auto_ptr_my<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
    wcout << **i << L'\t';

  return 0;
}

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


ШАГ 3 Как указано в KennyTM ниже:

добавьте этот код перед return 0; оператором:

std::vector<auto_ptr_my<int>> vec2 = vec;

for ( vector<auto_ptr_my<int>>::const_iterator i(vec2.cbegin()) ; i != vec2.cend() ; ++i )
  wcout << **i << L'\t';
wcout << std::endl;

for ( vector<auto_ptr_my<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
  wcout << **i << L'\t';
wcout << std::endl;

... и получить утечки памяти!


ЗАКЛЮЧЕНИЕ Иногда мы можем использовать auto_ptr с контейнерами без видимых сбоев ,иногда нет.Во всяком случае, это плохая практика.Но не забывайте, что auto_ptr спроектирован таким образом, что вы не можете использовать его прямо с контейнерами и алгоритмами STL: вам придется написать некоторый код-обертку.Наконец, использование auto_ptr с контейнерами STL - на ваш страх и риск.Например, некоторые реализации sort не приведут к сбою при обработке элементов vector, но другие реализации приведут непосредственно к сбою.

Этот вопрос имеет академические цели.Спасибо KennyTM за предоставленный пример аварии STEP 3!

0 голосов
/ 25 декабря 2011

Правильный ответ: «никогда не использовать auto_ptr вообще» - он устарел и никогда не стал частью стандарта, именно по указанным здесь причинам.Вместо этого используйте std :: unique_ptr.

0 голосов
/ 25 декабря 2011

Вывод: я даже не могу скомпилировать такой пример. Почему они мешают мне делать то, что я не могу скомпилировать ??

IIRC, все наоборот: поставщик компиляторов предпринимает шаги, чтобы помешать вам скомпилировать что-то, чего вы не должны делать. Как написано в стандарте, они могут реализовать библиотеку так, как код компилируется, а затем не работает должным образом. Они также могут реализовать его таким способом, который считается превосходным, потому что это один из тех немногих случаев, когда компилятору действительно разрешено запрещать вам делать глупости:)

...