' auto_ptr的'和STL容器:写一个错误用法的例子

时间:2011-12-25 16:59:43

标签: c++ stl vector auto-ptr

阅读本教程后提出了这个问题: http://www.cprogramming.com/tutorial/auto_ptr.html

在那里你可以找到以下声明:这种行为的一个微妙结果是auto _ ptrs在所有场景中都不能正常工作。例如,使用带有标准模板库的auto _ ptr对象可能会导致问题,因为STL中的某些函数可能会在容器(如向量容器类)中复制对象。一个例子是sort函数,它使容器中某些对象的副本被排序。因此,此副本可以轻松删除容器中的数据!

关于'auto_ptr'的大多数论文都告诉我们以下内容: “切勿在STL容器中使用'auto_ptr'!他们经常在执行内部操作时复制其元素。例如,考虑sort上的std::vector”。

所以我的目标是编写代码示例来说明这一点,或证明这些示例在理论上只是真实而且在实践中很奇怪

P.S。 @everybody_who_also_knows_that_auto_ptr_is_deprecated 我也知道这个。但是,您是否考虑过可能不允许使用新指针容器的技术原因(遗留代码或旧编译器)?而且这个问题是关于旧的和坏的(如果你愿意的话)auto_ptr

5 个答案:

答案 0 :(得分:4)

我现在没有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将接受const引用(§23.3.6.1[vector.overview] / 2)。

void push_back(const T& x);

所以不可能通过push_back构造auto_ptr,因为没有构造函数接受const引用。

答案 1 :(得分:0)

  

结论是:我甚至无法编译这样的例子。为什么他们阻止我做一些我无法编​​译的事情?

IIRC,反过来说:编译器供应商采取措施阻止您编译您不应该做的事情。标准的编写方式,他们可以以代码编译的方式实现库,然后无法正常工作。他们也可以用这种方式实现它,这被认为是优越的,因为它是少数几次实际允许编译器阻止你做一些愚蠢的事情之一:)

答案 2 :(得分:0)


第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&lt; Ty&gt;':没有可用的复制构造函数或复制构造函数被声明为'explicit'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可以与vectorsort一起使用,不会发生内存泄漏和崩溃


第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崩溃示例!

答案 3 :(得分:0)

根据你所写的内容,你似乎已经知道了有关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的比较lambda函数,它故意通过值接受它的参数,因此每次执行比较时都会销毁容器中的对象。

如果你改变了lambda以便它通过引用接收它的参数,如下所示,大多数STL实现实际上会表现正常,即使你做的事情在概念上是错误的。

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

答案 4 :(得分:-2)

正确的答案是“永远不要使用auto_ptr” - 它已被弃用,并且从未成为标准的一部分,正是由于此处列出的原因。请改用std :: unique_ptr。