Контейнеры 'auto_ptr' и STL: написание примера ошибочного использования

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

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

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

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

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

Ответ 1

У меня сейчас нет 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, потому что никакой конструктор не принимает константную ссылку.

Ответ 2

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

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

Ответ 3


ШАГ 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 текст ошибки следующий: Ошибка 0 Ошибка C2558: класс 'std::auto_ptr & lt; 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>
#include <memory>
#include <functional>

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<std::auto_ptr<T> *>(this)) = *(static_cast<std::auto_ptr<T> *>(const_cast<auto_ptr_my *>(&right)));
    return *this;
  }
  auto_ptr_my(const auto_ptr_my<T>& right) {
    *this = right;
  }
};

namespace std
{
template<> struct 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 пример сбоя!

Ответ 4

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

Поэтому я предполагаю, что ваш интерес к контейнерам auto_ptr является чисто ориентированным на обучение. Я понимаю ваше разочарование в попытке построить преднамеренный контрпример: на самом деле большинство исполнителей стандартных контейнеров создали рабочие обходы, чтобы избежать случайного запуска сломанной семантики 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 -safe. Скорее, в сравнительной лямбда-функции я перехожу к 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(); }); 

Удачи!

Ответ 5

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