智能指针类的线程安全复制赋值运算符

时间:2015-06-23 15:28:43

标签: c++ pointers exception copy-assignment

我正在实现一个智能指针类,并且有几个混淆。如果有人能帮我澄清,我真的很感激。

1:我认为智能指针类在构造函数中应该是“new”,在析构函数中应该是“delete”。但我似乎无法找到一个放置“新”的地方......所以用户将负责创建新的,而智能指针类有助于清理它?

2:在设计复制赋值运算符时,一种流行的方法是复制n-swap以便是线程安全的。但是,copy-n-swap要求对象按值传入(而不是通过引用)。它仍然可以用于设计智能指针吗?我担心的是这是一个指针类,因此可能无法通过值传递。但我对此并不十分肯定。

3:如果我被要求在面试时编写智能指针,我是否必须提供某种类型的引用计数?思想引用计数特定于shared_ptr ...

4:这一定是一个愚蠢的问题,但最好再问一下,然后内心怀疑。要SmartPointer<T>& sp访问ptr,是否应使用sp->ptrsp.ptr ??

感谢您的意见。

template <class T>
class SmartPointer{
    T* ptr;
public:
    SmartPointer():ptr(NULL){}
    SmartPointer(const T& p):ptr(p){}
    SmartPointer(const SmartPointer<T>& sp):ptr(sp->ptr){}
    SmartPointer<T>& operator=(const SmartPointer<T>& sp);
    ~SmartPointer(){delete ptr;}
    T& operator*(){return *ptr;}
    T* operator->(){return ptr;}
};

SmartPointer& operator=(const SmartPointer& sp){
    T* pOrig = ptr;
    ptr = new T(sp.ptr);
    delete pOrig;
    return *this;
}

2 个答案:

答案 0 :(得分:1)

  

因此用户将负责创建新的智能指针   上课有助于清理它吗?

是的,通常这就是所做的。例如:

std::unique_ptr<Foo> smart_ptr(new Foo);

构造(和分配)留给客户端的原因之一是因为T可以拥有各种参数化构造函数。至少可以说,这可能是棘手的,并且可能涉及可变参数构造函数模板以及一些非常奇特的模板魔法,如果你想允许一个智能指针自己构造T并具有所有可能性。让客户端在构造智能指针时传入指针变得更简单。只要他们在施工时立即这样做,那就安全了,因为如果operator new在我们进入智能指针构造之前抛出,就不会分配/构建任何东西,因此没有什么可以清理的除了已经在调用堆栈上的内容(为了异常安全,它应符合RAII)。

如果你想让它对API边界更加健壮,那么你通常想要捕获一个&#34;删除器&#34;这将调用&#39;运营商删除&#39;在生成代码的同一站点(在跨模块边界工作时很重要,其中尝试释放模块B中的内存以用于模块A中分配的内存将产生未定义的行为)。

  

但是,copy-n-swap要求对象按值传入(而不是通过   参考)。它仍然可以用于设计智能指针吗?我的   值得关注的是,这是一个指针类,因此可能无法做到   传递价值。但我对此并不十分肯定。

对于你正在制作的这种智能指针,它没有实现引用计数,通常最好的设计是禁止复制(分配和复制ctor,虽然移动ctors很好)。

否则,您将回到过时的转让所有权的做法(如古代std::auto_ptr的情况)或试图深入复制指针。

转移所有权可能特别容易出现人为错误,因为它将来源的复制视为可变(这是完全不寻常和冒犯的行为)。如果你这样做,你可以使用原子CAS来交换指针,但你需要让复制构造函数和赋值运算符通过引用接受事物,而不是const引用或值,因为它会将源视为可变的(要么是私有的ptr成员你有可变的并使用const引用)。

深度复制指针是一种有趣的想法,但有一个问题是有人可能会尝试在T不是完整类型(未定义,仅声明)的站点上复制智能指针。它类似于析构函数的问题,所以如果你想像这样做一个深度复制智能指针,一个强大的解决方案就是捕获T的复制构造函数(例如:存储一个函数指针指向你生成的函数)当您的智能指针被构造为克隆/复制构造T)的新元素时。

对现有的深层复制代码也进行了轻微修复:

SmartPointer& operator=(const SmartPointer& sp){
    T* pOrig = ptr;
    ptr = new T(*sp.ptr); <-- need to dereference here
    delete pOrig;
    return *this;
}
  

3:这一定是一个愚蠢的问题,但更好的问再保持怀疑   内。适用于SmartPointer&amp; sp访问ptr,如果它使用sp-&gt; ptr或   sp.ptr ??

让编译器解决你的疑虑。给定引用,SmartPointer& spsp->ptr在这种情况下将是编译器错误,除非T有一个名为ptr的成员可以访问,这可能不是你想要的。 sp->ptr会调用您重载的operator->,而不是访问智能指针的实际私有成员。 operator->通常只为指针定义,因此尝试在引用或const引用上使用它会调用重载的,用户定义的运算符。

答案 1 :(得分:1)

1:我会说构造函数就是这样的地方:

SmartPointer(T* p):ptr(p){}

然后您获得与典型智能指针相同的语法 - 您可以通过调用SmartPointer objPtr(new Obj());来创建它。用户可以随心所欲地创建他的对象,并且智能指针负责在他之后清理它(因为它是智能指针旨在帮助的主要问题)。请注意,将来您可能需要添加某种std::make_shared()方法,以避免在构建和提高性能时复制包装器。

2:您必须考虑智能指针的行为方式。请记住,当您尝试复制原始指针时,只复制地址,而不是对象本身。因此,如果您决定将SmartPointer视为配备垃圾收集器的普通指针,那么您可以自由地实现copy-n-swap。另一方面,您可以删除std::unique_ptr中的 operator =()或尝试实现std::shared_ptr中的引用计数。

3:只有当你被明确要求时,我才会说。通常通过智能指针可以理解某种能够自己控制原始指针生命周期的包装器。虽然,我确定他们是否会问“请写一个简单的智能指针的实现”,然后你会问“你想让它处理共享所有权吗?”,你会得到一点:)

4:sp.ptr当然:) SmartPointer<T>& sp是一个引用,因此为了访问其成员,您必须使用点.运算符。

要使用sp->ptr,您必须拥有SmartPointer<T>* sp参数。