3

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

在那里您可以找到以下声明:这种行为的一个微妙后果是auto_ptrs在所有情况下都不能正常工作。例如,将auto_ptr对象与标准模板库一起使用可能会导致问题,因为 STL 中的某些函数可能会复制容器中的对象,例如向量容器类。一个例子是 sort 函数,它复制正在排序的容器中的一些对象。这样一来,这个副本就可以爽快地删除容器中的数据了!

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

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

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

4

5 回答 5

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 引用。

于 2011-12-25T17:46:49.183 回答
1

STEP 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< Ty>': 没有可用的复制构造函数或复制构造函数被声明为“显式” c:\program files (x86)\microsoft visual studio 11.0\vc\include\xmemory0 608

结论是:我什至无法编译这样的例子。为什么他们阻止我做一些我无法编​​译的事情?他们的预防措施并不总是正确的。


第2步

由于设计原因,我们不能直接auto_ptr用作元素类型。但是我们可以用下面介绍的方式包装`auto_ptr'。vectorauto_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的情况下使用容器,有时则不能。无论如何,这是不好的做法。但是不要忘记,它的设计方式不能直接与 STL 容器和算法一起使用:相反,您必须编写一些包装器代码。最后,使用STL 容器需要您自担风险。例如,某些实现在处理元素时不会导致崩溃,但其他实现会直接导致崩溃。auto_ptrauto_ptrsortvector

这个问题有学术目的。感谢 KennyTM 提供 STEP 3 崩溃示例!

于 2011-12-26T05:03:05.247 回答
1

从您所写的内容来看,您似乎已经了解了有关auto_ptrs 容器以及它们为何不安全的所有信息。

因此,我假设您对auto_ptrs 容器的兴趣纯粹是面向教学的。我理解您在尝试构建一个有意的反例时的挫败感:事实上,大多数标准容器的实现者都已经制定了变通方法以避免意外触发auto_ptrs 的损坏语义。

因此,这是我自己为教学而编写的一个示例:

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安全的。而是在我传递给的比较 lambda 函数中std::sort,它故意按值接受其参数,因此每次执行比较时都会破坏容器中的对象。

如果您更改 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(); }); 

祝你好运!

于 2016-07-11T14:31:20.363 回答
0

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

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

于 2011-12-25T18:35:20.650 回答
-2

正确的答案是“根本不使用 auto_ptr”——它已被弃用并且根本没有成为标准的一部分,正是出于此处概述的原因。请改用 std::unique_ptr 。

于 2011-12-25T19:53:35.043 回答