如何在需要进行复制构造的类中使用std::auto_ptr?

8

我有一个类foo,其中包含一个std::auto_ptr成员,我想进行复制构造,但似乎不允许这样做。赋值也有类似的问题。请看下面的示例:

struct foo
{
private:
    int _a;
    std::string _b;
    std::auto_ptr< bar > _c;

public:
    foo(const foo& rhs)
        :   _a(rhs._a)
        ,   _b(rhs._b)

        ,   _c(rhs._c)
                // error: Cannot mutate rhs._c to give up ownership - D'Oh!
    {
    }

    foo& operator=(const foo& rhs)
    {
         _a = rhs._a;
         _b = rhs._b;

         _c = rhs._c;
             // error: Same problem again.
    }
};

我可以将_c声明为mutable,但我不确定这是否正确。有没有更好的解决方案?

编辑

好吧,我没有得到我期望的答案,所以我会更具体地说明问题。

  • 在堆栈上创建了一个foo类型的对象,并按值传递到容器类(不是stl),然后超出范围。我无法控制容器代码。(实际上是带错误的活动队列实现。)
  • bar类是一个相当重量级的解析器。它在newdelete方面性能非常差,因此即使它是可复制的,它也会非常昂贵。
  • 我们可以保证当创建bar对象时,它只需要在1个地方拥有所有权。在这种情况下,它在线程之间传递并在完成事务时删除。这就是我希望使用std::autp_ptr的原因。
  • 我非常愿意考虑boost智能指针,但如果有其他选择,我希望保证这种唯一性。
9个回答

16

你可能想尝试下面的代码:

    foo(const foo& rhs)
        :   _a(rhs._a)
        ,   _b(rhs._b)
        ,   _c(_rhs._c.get() ? new bar(*_rhs._c.get()) : 0)
    {
    }

(赋值操作符类似。)

但是,这仅在bar是可复制构造的,并且如果确实做到了你想要的结果,才能起作用。问题在于,两个foo对象(_rhs和已构造的对象)在_c中将有不同的指针。

如果您希望它们共享指针,则不能使用auto_ptr,因为它不支持共享所有权。例如,考虑在这种情况下使用shared_ptr来自于Boost.SmartPtr(这将包含在新的C++标准中)。或者使用其他共享指针实现,因为这是一个常见的概念,有很多实现可供选择。


即使在编辑后,仍然是最佳答案。foo将需要复制或共享bar,而auto_ptr<bar>不擅长共享。由于现在排除了复制,因此您将需要一个shared_ptr。 - MSalters

8

正如您发现的那样,您不能这样复制std :: auto_ptr。复制后指向对象的所有权归谁?相反,您应该使用引用计数智能指针。 Boost库 有一个shared_ptr 可以使用。


3

首先,我建议避免使用auto_ptr

在某些情况下,所有权的转移是有好处的,但我发现这种情况很少见,而且现在已经有“完整的”智能指针库可以轻松使用了(如果我没记错的话,auto_ptr只是为了在标准库中包含至少一个示例,而不需要等待好的实现所带来的延迟)。

例如,在这里
或者在这里

确定语义
foo的副本是否应该持有对bar相同实例的引用?如果是这样,请使用boost::shared_ptr或(boost::intrusive_ptr),或类似的库。

还是应该创建深拷贝? (有时可能需要这样做,例如当具有延迟创建状态时)。我不知道任何标准实现这个概念的方法,但是构建类似于现有智能指针的方法并不复杂。

   // roughly, incomplete, probably broken:
   template <typename T>
   class deep_copy_ptr
   {
      T * p;
     public:
      deep_copy_ptr()  : p(0) {}
      deep_copy_ptr(T * p_)  : p(p_) {}
      deep_copy_ptr(deep_copy_ptr<T> const & rhs)  
      {
        p = rhs.p ? new T(*rhs.p) : 0;
      }
      deep_copy_ptr<T> & operator=(deep_copy_ptr<T> const & rhs)
      {
         if (p != rhs.p)
         {
           deep_copy_ptr<T> copy(rhs);
           swap(copy);
         }
      }
      // ...
   }

1
如果我有一个包含auto_ptr的类,并且想要深度复制语义,通常只对具有虚拟复制运算符(即clone())的类执行此操作。
然后,在复制构造函数中,我将auto_ptr初始化为其他对象的clone();例如:
class Foo
{
   public:
      Foo(const Foo& rhs) : m_ptr(rhs.m_ptr->clone());
   private:
      std::auto_ptr<T> m_ptr;
};

clone() 通常的实现方式如下:

class T
{
   std::auto_ptr<T> clone() const
   {
      return std::auto_ptr<T>(new T(*this));
   }
};

我们强制要求T是可克隆的,但这个条件本质上是通过具有auto_ptr成员的可复制类来实现的。


1

std::auto_ptr 是 C++ 中管理动态对象的好工具,但为了有效使用它,重要的是要理解 auto_ptr 的工作原理。本文解释了这个智能指针应该在何时、何地使用。

在您的情况下,首先您应该决定您想如何处理 auto_ptr 内部的对象。它应该被克隆还是共享?

如果应该被克隆,请确保它有一个复制构造函数,然后创建一个包含对象副本的新 auto_ptr,参见 Adam Badura 的答案。

如果应该共享,您应该像 Martin Liversage 建议的那样使用 boost::shared_ptr


0
整个auto_ptr的想法是只有一个对象的所有者。这意味着您不能复制指针而不删除原始所有权。
由于您无法复制它,因此您也无法复制包含auto_ptr的对象。
您可以尝试使用移动语义,例如使用std::swap而不是复制。

-1,复制一个包含 auto_ptr<T> 的对象非常简单。您可以通过创建指向已复制的T的新auto_ptr<T>来完成。 - MSalters
@MSalters:但这样你并没有复制auto_ptr。你只是复制了所引用的对象。 - xtofl

0

我的第一选择是在这种情况下完全避免使用auto_ptr。但如果我被逼到墙角,我可能会尝试在_c的声明中使用关键字mutable - 这将允许即使从const引用也可以修改它。


0

鉴于修改后的情况,您似乎想要实现所有权语义的转移。

如果是这样的话,您需要让您的复制构造函数和赋值运算符接受非 const 引用作为它们的参数,并在那里执行初始化/赋值操作。


-1

在涉及到 auto_ptr<> 的复制构造函数或赋值运算符中,您不能使用 const 引用。请移除 const。换句话说,使用以下声明:

foo(foo & rhs);
foo & operator=(foo & rhs);

这些形式在标准中明确提到,主要在第12.8节中。它们应该可在任何符合标准的实现中使用。事实上,12.8的第5段和第10段表明,如果任何成员需要,则隐式定义的复制构造函数和赋值运算符(分别)将采用非常量引用。

当然可以使用它们;您只需要覆盖隐式复制构造函数和赋值运算符。 - JohnMcG
根据标准,它们是隐式复制构造函数和赋值运算符。我提供了参考。如果您知道无法在某些实现上工作,请告诉我们。 - David Thornley
你的实现将会起作用,我认为我对隐式生成部分的看法是错误的。我不同意的是第一句话,即你不能在使用auto_ptr的复制构造函数中使用const引用。你可以这样做,只是需要让它们执行与默认行为不同的操作。 - JohnMcG
你想做什么?你不能只是从一个const参数中复制auto_ptr<>。你可以进行深度复制,但这里并不需要。你可以创建两个指向同一对象的auto_ptr<>,这是一场灾难的等待。 - David Thornley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接