2

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

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

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;
}
4

2 回答 2

1

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

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

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 来交换指针,但你需要让复制构造函数和赋值运算符通过引用而不是 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& sp 要访问 ptr,应该使用 sp->ptr 还是 sp.ptr?

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

于 2015-06-23T15:43:54.127 回答
1

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

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

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


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


3:我会说只有当你被明确要求这样做时。通常通过智能指针,人们可以理解某种能够自行控制原始指针生命周期的包装器。虽然,我确定如果他们问“请编写一个简单的智能指针的实现”并且您问“您是否希望它也处理共享所有权?”,您将获得一分:)


4:sp.ptr当然 :)SmartPointer<T>& sp是一个参考,所以为了访问它的成员,你必须使用点.运算符。

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

于 2015-06-23T16:07:12.357 回答