簡體   English   中英

使用 boost::shared_ptr 時有哪些潛在危險?

[英]What are potential dangers when using boost::shared_ptr?

使用boost::shared_ptr時,有哪些方法可以讓你在腳上開槍? 換句話說,當我使用boost::shared_ptr時,我必須避免哪些陷阱?

循環引用: shared_ptr<> shared_ptr<> 當然,您可以使用weak_ptr<>來打破這個循環。


我添加以下內容作為我在評論中談論的示例。

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        if (parent_) parent_->frob();
    }

private :
    void do_frob();
    shared_ptr<node> parent_;
    vector< shared_ptr<node> > children_;
};

在這個例子中,你有一個節點樹,每個節點都有一個指向其父節點的指針。 無論出於何種原因, frob() 成員 function 在樹中向上波動。 (這並不完全古怪;一些 GUI 框架以這種方式工作)。

問題是,如果你失去了對最頂層節點的引用,那么最頂層節點仍然持有對其子節點的強引用,並且它的所有子節點也持有對其父節點的強引用。 這意味着存在循環引用阻止所有實例自行清理,而實際上無法從代碼中到達樹,這個 memory 泄漏。

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
        if (parent) parent->frob();
    }

private :
    void do_frob();
    weak_ptr<node> parent_; // Note: now a weak_ptr<>
    vector< shared_ptr<node> > children_;
};

在這里,父節點已被弱指針替換。 它不再對它所指的節點的生命周期有發言權。 因此,如果最上面的節點像前面的示例一樣超出 scope,那么雖然它持有對其子節點的強引用,但它的子節點不持有對其父節點的強引用。 因此,沒有對 object 的強引用,它會自行清理。 反過來,這會導致孩子們失去他們的一個強參考,這導致他們清理,等等。 簡而言之,這不會泄漏。 只需戰略性地將 shared_ptr<> 替換為 weak_ptr<>。

注意:上述內容同樣適用於 std::shared_ptr<> 和 std::weak_ptr<>,就像它適用於 boost::shared_ptr<> 和 boost::weak_ptr<> 一樣。

為同一個 object 創建多個不相關的shared_ptr

#include <stdio.h>
#include "boost/shared_ptr.hpp"

class foo
{
public:
    foo() { printf( "foo()\n"); }

    ~foo() { printf( "~foo()\n"); }
};

typedef boost::shared_ptr<foo> pFoo_t;

void doSomething( pFoo_t p)
{
    printf( "doing something...\n");
}

void doSomethingElse( pFoo_t p)
{
    printf( "doing something else...\n");
}

int main() {
    foo* pFoo = new foo;

    doSomething( pFoo_t( pFoo));
    doSomethingElse( pFoo_t( pFoo));

    return 0;
}

構造一個匿名臨時共享指針,例如在 arguments 內部對 function 調用:

f(shared_ptr<Foo>(new Foo()), g());

這是因為允許執行new Foo() ,然后調用g() ,然后 g( g()拋出異常,而不會設置shared_ptr ,因此shared_ptr沒有機會清理Foo object。

小心將兩個指針指向同一個 object。

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer

b->doSomething(); // crashes

而是使用這個

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d = 
    boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--

b->doSomething(); // no crash

此外,任何持有 shared_ptrs 的類都應該定義復制構造函數和賦值運算符。

不要嘗試在構造函數中使用 shared_from_this() ——它不起作用。 而是創建一個 static 方法來創建 class 並讓它返回一個 shared_ptr。

我已經毫無問題地傳遞了對 shared_ptrs 的引用。 只需確保它在保存之前已被復制(即,沒有作為 class 成員的引用)。

這里有兩件事要避免:

  • 調用get() function 獲取原始指針並在指向的 object 超出 scope 后使用它。

  • 傳遞對shared_ptr的引用或原始指針也應該是危險的,因為它不會增加有助於保持 object 存活的內部計數。

我們調試了幾個星期的奇怪行為。

原因是:
我們將“this”傳遞給了一些線程工作者,而不是“shared_from_this”。

不完全是一支步槍,但肯定是令人沮喪的根源,直到您圍繞如何以 C++0x 方式進行操作:您從<functional>了解和喜愛的大多數謂詞都不能很好地與shared_ptr配合使用。 令人高興的是, std::tr1::mem_fn與對象、指針和shared_ptr一起工作,取代了std::mem_fun ,但如果你想使用std::negatestd::not1std::plus或任何那些老朋友使用shared_ptr ,准備好使用std::tr1::bind和可能的參數占位符。 實際上,這實際上更通用,因為現在您基本上最終對每個 function object 適配器使用bind ,但如果您已經熟悉 STL 的便利功能,則需要一些時間來適應。

這篇 DDJ 文章涉及該主題,並提供了大量示例代碼。 幾年前,當我第一次不得不弄清楚如何做時,我也在博客上寫過它。

如果堆上有很多小對象但它們並不是真正“共享”的,那么對非常小的對象(如char short )使用shared_ptr可能會產生開銷。 boost::shared_ptr為它在 g++ 4.4.3 和帶有 Boost 1.42 的 VS2008 上創建的每個新引用計數分配 16 個字節。 std::tr1::shared_ptr分配 20 個字節。 現在,如果您有一百萬個不同的shared_ptr<char> ,這意味着您的 memory 的 2000 萬字節只保留了 count=1。 更不用說間接成本和 memory 碎片。 在您最喜歡的平台上嘗試以下操作。

void * operator new (size_t size) {
  std::cout << "size = " << size << std::endl;
  void *ptr = malloc(size);
  if(!ptr) throw std::bad_alloc();
  return ptr;
}
void operator delete (void *p) {
  free(p);
}

在 class 定義中為此提供 shared_ptr< T > 也是危險的。 請改用 enabled_shared_from_this。

此處查看以下帖子

在多線程代碼中使用shared_ptr時需要小心。 當幾個shared_ptr指向同一個 memory 被不同的線程使用時,就相對容易陷入這種情況。

shared_ptr 的流行廣泛使用幾乎不可避免地會導致不必要的和看不見的 memory 占用。

循環引用是眾所周知的原因,其中一些可能是間接的且難以發現,尤其是在由多個程序員編寫的復雜代碼中; 程序員可能會決定一個 object 需要引用另一個作為快速修復,並且沒有時間檢查所有代碼以查看他是否正在關閉一個循環。 這種危險被大大低估了。

不太為人所知的是未發布參考的問題。 如果一個 object 被共享給許多 shared_ptrs,那么它不會被銷毀,直到它們中的每一個都歸零或超出 scope。 很容易忽略其中一個引用,並最終得到潛伏在 memory 中看不見的對象,您認為您已經完成了。

雖然嚴格來說這些不是 memory 泄漏(它將在程序退出之前全部釋放)它們同樣有害且更難檢測。

這些問題是權宜之計錯誤聲明的后果: 1. 將您真正想要的單一所有權聲明為 shared_ptr。 scoped_ptr 是正確的,但是任何其他對該 object 的引用都必須是原始指針,它可能會懸空。 2. 將你真正想要的被動觀察引用聲明為 shared_ptr。 weak_ptr 是正確的,但是每次你想使用它時,你都會很麻煩地將它轉換為 share_ptr。

我懷疑你的項目是這種做法會給你帶來的麻煩的一個很好的例子。

如果您有一個 memory 密集型應用程序,您確實需要單一所有權,以便您的設計可以明確控制 object 生命周期。

單一所有權 opObject=NULL; 肯定會刪除 object 並且它現在會這樣做。

共享所有權 spObject=NULL; ........誰知道?......

如果您有共享對象的注冊表(例如,所有活動實例的列表),則這些對象將永遠不會被釋放。 解決方案:在循環依賴結構的情況下(參見 Kaz Dragon 的回答),酌情使用weak_ptr。

智能指針不是萬能的,原始指針不能被淘汰

可能最嚴重的危險是,由於shared_ptr是一個有用的工具,人們會開始把它放在任何地方。 由於普通指針可能被濫用,同樣的人會尋找原始指針並嘗試用字符串、容器或智能指針替換它們,即使它沒有意義。 原始指針的合法使用將變得可疑。 會有指針警察。

這不僅可能是最嚴重的危險,而且可能是唯一的嚴重危險。 shared_ptr的所有最嚴重濫用將是智能指針優於原始指針(無論這意味着什么)這一想法的直接后果,並且將智能指針放在任何地方將使 C++ 編程“更安全”。

當然,智能指針需要轉換為原始指針才能使用這一事實駁斥了智能指針邪教的這種說法,但原始指針訪問在operator*operator-> (或在get()中顯式,但在隱式轉換中沒有隱式,足以給人這樣的印象,即這不是真正的轉換,並且這種非轉換產生的原始指針是無害的臨時指針。

C++ 不能成為“安全語言”,並且 C++ 沒有有用的子集是“安全的”

Of course the pursuit of a safe subset ("safe" in the strict sense of "memory safe", as LISP, Haskell, Java...) of C++ is doomed to be endless and unsatisfying, as the safe subset of C++ is tiny而且幾乎沒用,因為不安全的原語是規則而不是例外。 C++ 中的嚴格 memory 安全意味着沒有指針,只有自動存儲 class 的引用 但是在程序員被定義信任的語言中,有些人會堅持使用一些(原則上)防白痴的“智能指針”,即使在原始指針沒有其他優勢的情況下,這是一種擰緊程序 state 的特定方法被避免。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM