簡體   English   中英

C ++使用scoped_ptr作為成員變量

[英]C++ using scoped_ptr as a member variable

只是想要關於設計問題的意見。 如果您擁有一個C ++類而不是擁有其他對象,那么您會使用智能指針來實現這一點嗎?

class Example {
public: 
  // ...

private:
  boost::scoped_ptr<Owned> data;
};

“擁有的”對象無法按值存儲,因為它可能會在對象的整個生命周期內發生變化。

我的觀點是,一方面,您可以清楚地知道該對象已擁有並確保將其刪除,但另一方面,您可以輕松地擁有一個常規指針並將其刪除到析構函數中。 這是過度殺傷力嗎?

后續行動:只想對您的所有回答表示感謝。 感謝您對有關auto_ptr的注意,在復制整個對象時將另一個對象留有NULL指針,我已經廣泛使用了auto_ptr,但還沒有想到。 除非我有充分的理由,否則我基本上將所有類都設為boost :: noncopyable,因此不必擔心。 同時也感謝您提供有關異常中內存泄漏的信息,這也很高興。 我盡量不要寫任何可能在構造函數中導致異常的東西-這樣做有更好的方法-這樣就不成問題。

我只是有另一個問題。 當我問這個問題時,我想知道是否有人真正做到了這一點,而且大家似乎都提到從理論上講這是一個好主意,但是沒有人說他們實際上做了。 這讓我感到驚訝! 當然,擁有指向另一個物體的指針的對象並不是一個新主意,我希望大家在某個時候都可以做到這一點。 這是怎么回事?

scoped_ptr對此非常有用。 但是必須理解它的語義。 您可以使用兩個主要屬性對智能指針進行分組:

  • 可復制:可以復制智能指針:副本和原始共享所有權。
  • 可移動:可移動智能指針:移動結果將擁有所有權,原始將不再擁有。

那是相當普遍的術語。 對於智能指針,有一個特定的術語可以更好地標記這些屬性:

  • 所有權轉讓:智能指針是可移動的
  • 所有權共享:智能指針是可復制的。 如果智能指針已經是可復制的,則很容易支持所有權轉移語義:這僅是原子復制和原始復位操作,將其限制為某些類型的智能指針(例如,僅臨時智能指針)。

讓我們使用(C)opyable(M)ovable(N)either對可用的智能指針進行(C)opyable

  1. boost::scoped_ptr :N
  2. std::auto_ptr :M
  3. boost::shared_ptr :C

auto_ptr有一個大問題,因為它使用復制構造函數實現了Movable概念。 這是因為當auto_ptr被C ++接受時,與新的C ++標准相對,還沒有一種方法可以使用move構造函數來本地支持move語義。 也就是說,您可以使用auto_ptr執行以下操作,並且可以正常工作:

auto_ptr<int> a(new int), b;
// oops, after this, a is reset. But a copy was desired!
// it does the copy&reset-of-original, but it's not restricted to only temporary
// auto_ptrs (so, not to ones that are returned from functions, for example).
b = a; 

無論如何,正如我們所見,在您的情況下,您將無法將所有權轉讓給另一個對象:您的對象實際上將是不可復制的。 並且在下一個C ++標准中,如果您繼續使用scoped_ptr,它將是不可移動的。

為了使用scoped_ptr實現您的類,請注意滿足以下兩個條件之一:

  • 在類的.cpp文件中編寫一個析構函數(即使它為空),或者
  • 使自己Owned一個完全定義的類。

否則,當您創建Example對象時,編譯器將為您隱式定義一個析構函數,該析構函數將調用scoped_ptr的析構函數:

~Example() { ptr.~scoped_ptr<Owned>(); }

然后,這將使scoped_ptr調用boost::checked_delete ,如果您沒有完成以上兩點中的任何一項,則會抱怨Owned不完整。 如果在.cpp文件中定義了自己的dtor,則將從.cpp文件進行對scoped_ptr的析構函數的隱式調用,您可以在其中放置Owned類的定義。

您對auto_ptr遇到同樣的問題,但還有另一個問題:為auto_ptr提供不完整的類型當前是未定義的行為(也許將在下一個C ++版本中得到修復)。 因此,當您使用auto_ptr時, 必須在標頭文件中將“擁有的個人”設置為完整類型。

shared_ptr沒問題,因為它使用了多態刪除器,該刪除器對刪除進行了間接調用。 因此,刪除函數不會在實例化析構函數時實例化,而是在shared_ptr的構造函數中創建刪除程序時才會實例化。

這是一個好主意。 它有助於簡化代碼,並確保當您在對象的生存期內更改“擁有”對象時,前一個對象被正確銷毀了。

您必須記住,scoped_ptr是不可復制的,這使您的類在默認情況下不可復制,直到/除非您添加自己的副本構造函數,等等。(當然,在使用原始指針的情況下,使用默認副本構造函數將是非復制操作,也不行!)

如果您的類具有多個指針字段,那么在以下情況下,使用scoped_ptr實際上可以提高異常安全性:

class C
{
  Owned * o1;
  Owned * o2;

public:
  C() : o1(new Owned), o2(new Owned) {}
  ~C() { delete o1; delete o2;}
};

現在,想象一下在構造C的過程中,第二個“新擁有的”會拋出異常(例如,內存不足)。 o1將被泄漏,因為不會調用C ::〜C()(析構函數),因為該對象尚未完全構造。 確實會調用任何完全構造的成員字段的析構函數。 因此,使用scoped_ptr而不是普通指針將允許o1被正確銷毀。

一點也不算過分,這是個好主意。

但是,這確實需要您的班級客戶了解升壓。 這可能是問題,也可能不是問題。 對於可移植性,您可以考慮使用std :: auto_ptr來完成(在這種情況下)相同的工作。 由於它是私人的,因此您不必擔心其他人試圖復制它。

使用scoped_ptr是一個好主意。

保持和手動銷毀指針並不像您想的那么簡單。 特別是如果您的代碼中有多個RAW指針。 如果異常安全性和不泄漏內存是優先事項,那么您需要大量額外的代碼來使其正確。

首先,必須確保正確定義所有四個默認方法。 這是因為這些方法的編譯器生成版本適用於普通對象(包括智能指針),但在正常情況下會導致指針處理問題(查找“淺復制問題”)。

  • 默認構造函數
  • 復制構造函數
  • 賦值運算符
  • 析構函數

如果使用scoped_ptr,則無需擔心其中任何一個。

現在,如果您的類中有多個RAW指針(或構造函數的其他部分可以拋出)。 您必須在構造和銷毀過程中明確處理異常。

class MyClass
{
    public:
        MyClass();
        MyClass(MyClass const& copy);
        MyClass& operator=(MyClass const& copy);
        ~MyClass();

    private
        Data*    d1;
        Data*    d2;
};

MyClass::MyClass()
    :d1(NULL),d2(NULL)
{
    // This is the most trivial case I can think off
    // But even it looks ugly. Remember the destructor is NOT called
    // unless the constructor completes (without exceptions) but if an
    // exception is thrown then all fully constructed object will be
    // destroyed via there destructor. But pointers don't have destructors.
    try
    {
        d1 = new Data;
        d2 = new Data;
    }
    catch(...)
    {
        delete d1;
        delete d2;
        throw;
    }
}

看看scopted_ptr有多容易。

有作用域的指針正是因為它可以確保刪除對象而無需擔心程序員的情況,因此可以很好地做到這一點。 我認為這是作用域ptr的很好使用。

我發現,一個好的設計策略通常是避免盡可能多地手動釋放內存,並讓您的工具(在這種情況下為智能指針)為您完成任務。 正如我所看到的,手動刪除是不好的,主要原因之一是,代碼變得很難快速維護。 內存的分配和釋放邏輯通常在代碼中是分開的,這導致互補行無法保持在一起。

我認為這並不過分,它比具有原始指針更好地記錄了成員的語義,並且不容易出錯。

為什么要大刀闊斧? boost :: scoped_ptr非常易於優化,我敢打賭,生成的機器代碼將與您在析構函數中手動刪除指針相同。

scoped_ptr很好-只需使用它即可:)

暫無
暫無

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

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