繁体   English   中英

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

[英]thread-safe copy assignment operator for smart pointer class

我正在实现一个智能指针类,但有一些困惑。 如果人们能帮我澄清,我将不胜感激。

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

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

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

4:这一定是一个愚蠢的问题,但最好先问,然后再把疑虑藏在心里。 对于SmartPointer<T>& sp访问 ptr,它应该使用sp->ptr还是sp.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;
}

那么用户将负责创建新的,而智能指针类有助于清理它?

是的,通常就是这样做的。 例子:

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

构造(和分配)留给客户端的原因之一是因为 T 可以具有各种参数化构造函数。 至少可以说这很棘手,并且如果您希望允许智能指针以所有可能的方式自行构造 T,则可能涉及带有一些非常奇特的模板魔术的可变参数构造函数模板。 让客户端在构造智能指针时传入指针更简单。 只要他们在构造时立即这样做,它就是安全的,因为如果operator new在我们进入智能指针构造之前抛出异常,则不会分配/构造任何内容,因此除了调用中已经存在的内容之外,将没有任何内容需要清理堆栈(为了异常安全,它应该符合 RAII)。

如果你想让它对 API 边界健壮,那么你通常希望捕获一个“删除器”,它会在生成代码的同一站点调用“操作符删除”(在跨模块边界尝试释放内存时很重要)在模块 B 中分配在模块 A 中的内存会产生未定义的行为)。

但是,copy-n-swap 要求对象按值(而不是按引用)传入。 它还能用于设计智能指针吗? 我担心这是一个指针类,因此可能无法按值传入。 但我对此不太确定。

对于您正在制作的这种不实现引用计数的智能指针,通常最好的设计是禁止复制(赋值和复制 ctor,尽管移动 ctor 很好)。

否则,您将回到过时的转让所有权的做法(如古老的std::auto_ptr )或尝试深度复制指针对象。

转让所有权特别容易出现人为错误,因为它将其复制的源视为可变的(这是完全不寻常且令人毛骨悚然的行为)。 如果你这样做,你可以使用原子 CAS 来交换指针,但你需要让复制构造函数和赋值运算符通过引用接受事物,而不是常量引用或值,因为它会将源视为可变的(要么是或使您拥有可变的私有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& sp 访问ptr,应该使用sp->ptr 还是sp.ptr??

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

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

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

然后你获得与典型智能指针相同的语法——你通过调用SmartPointer objPtr(new Obj());创建它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参数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM