'auto_ptr'和 STL 容器:编写错误用法示例



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

这种行为的一个微妙后果是auto_ ptrs并不是在所有情况下都能很好地工作。例如,在标准模板库中使用auto _ptr对象可能会导致问题,因为STL中的某些函数可能会复制容器(如vector容器类)中的对象。一个例子是sort函数,它复制正在排序的容器中的一些对象。因此,这个副本可以轻松地删除容器中的数据!

大多数关于auto_ptr的论文告诉我们如下内容:永远不要在STL容器中使用auto_ptr !它们经常在执行内部操作时复制其元素。例如,考虑sortstd::vector的影响。

所以我的目标是编写代码示例来说明这一点,或者证明这样的示例只在理论上是正确的,在实践中是奇怪的。

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

我现在没有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引用。

从你写的来看,似乎你已经知道了关于auto_ptr容器的一切,以及为什么它们是不安全的。

因此,我假设您对 auto_ptrs容器的兴趣纯粹是教学导向的。我理解您在试图构建一个故意的反例时的挫败感:事实上,大多数标准容器的实现者都已经采取了解决方案,以避免意外触发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编译它会得到一个很好的分段错误的可执行文件。

你可以使用类型演绎将上面的例子重写得更简洁:
  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(); }); 

祝你好运!


步骤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>':没有复制构造函数可用或复制构造函数声明为'explicit' c:program files (x86)microsoft visual studio 11.0vcincludexmemory0 608

结论是:我甚至不能编译这样的例子。为什么他们阻止我做一些我不能编译的东西??他们的预防措施并不总是正确的。


步骤2

由于auto_ptr的设计,我们不能直接使用auto_ptr作为vector元素类型。但是我们可以用下面的方式包装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可以与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提供的第3步崩溃示例!

结论是:我甚至不能编译这样的例子。为什么他们阻止我做一些我不能编译的东西??

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

正确的答案是"永远不要使用auto_ptr"——它被弃用了,根本没有成为标准的一部分,原因正是这里概述的。使用std::unique_ptr代替。

最新更新