簡體   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