簡體   English   中英

工廠功能的最佳智能指針返回類型是什么?

[英]What is the best smart pointer return type for a factory function?

關於智能指針和新的C ++ 11/14功能,我想知道具有這些功能的類的最佳實踐返回值和函數參數類型是什么:

  1. 一個工廠函數(在類之外),它創建對象並將它們返回給類的用戶。 (例如,打開文檔並返回可用於訪問內容的對象。)

  2. 實用程序函數接受來自工廠函數的對象,使用它們,但不取得所有權。 (例如,計算文檔中單詞數量的函數。)

  3. 在返回對象后保持對對象引用的函數(如獲取對象副本的UI組件,以便它可以根據需要在屏幕上繪制內容。)

工廠功能的最佳返回類型是什么?

  • 如果它是一個原始指針,用戶將必須正確delete它,這是有問題的。
  • 如果它返回unique_ptr<>則用戶無法共享它。
  • 如果它是shared_ptr<>那么我是否必須在各地傳遞shared_ptr<>類型? 這就是我現在正在做的事情,它導致了問題,因為我正在獲取循環引用,防止對象被自動銷毀。

效用函數的最佳參數類型是什么?

  • 我想通過引用傳遞將避免不必要地增加智能指針引用計數,但這有什么缺點嗎? 想到的主要原因是它阻止我將派生類傳遞給帶有基類類型參數的函數。
  • 有沒有什么方法可以讓調用者明白它不會復制對象? (理想情況下,如果函數體嘗試復制對象,代碼將無法編譯。)
  • 有沒有辦法使它獨立於使用的智能指針類型? (也許拿一個原始指針?)
  • 是否有可能有一個const參數,以明確該函數不會修改對象,而不會破壞智能指針兼容性?

保持對象引用的函數的最佳參數類型是什么?

  • 我猜這里是shared_ptr<>唯一的選擇,這可能意味着工廠類也必須返回一個shared_ptr<> ,對吧?

這里有一些代碼可以編譯並希望說明要點。

#include <iostream>
#include <memory>

struct Document {
    std::string content;
};

struct UI {
    std::shared_ptr<Document> doc;

    // This function is not copying the object, but holding a
    // reference to it to make sure it doesn't get destroyed.
    void setDocument(std::shared_ptr<Document> newDoc) {
        this->doc = newDoc;
    }
    void redraw() {
        // do something with this->doc
    }
};

// This function does not need to take a copy of the Document, so it
// should access it as efficiently as possible.  At the moment it
// creates a whole new shared_ptr object which I feel is inefficient,
// but passing by reference does not work.
// It should also take a const parameter as it isn't modifying the
// object.
int charCount(std::shared_ptr<Document> doc)
{
    // I realise this should be a member function inside Document, but
    // this is for illustrative purposes.
    return doc->content.length();
}

// This function is the same as charCount() but it does modify the
// object.
void appendText(std::shared_ptr<Document> doc)
{
    doc->content.append("hello");
    return;
}

// Create a derived type that the code above does not know about.
struct TextDocument: public Document {};

std::shared_ptr<TextDocument> createTextDocument()
{
    return std::shared_ptr<TextDocument>(new TextDocument());
}

int main(void)
{
    UI display;

    // Use the factory function to create an instance.  As a user of
    // this class I don't want to have to worry about deleting the
    // instance, but I don't really care what type it is, as long as
    // it doesn't stop me from using it the way I need to.
    auto doc = createTextDocument();

    // Share the instance with the UI, which takes a copy of it for
    // later use.
    display.setDocument(doc);

    // Use a free function which modifies the object.
    appendText(doc);

    // Use a free function which doesn't modify the object.
    std::cout << "Your document has " << charCount(doc)
        << " characters.\n";

    return 0;
}

工廠功能的最佳返回類型是什么?

unique_ptr是最好的。 它可以防止意外泄漏,並且用戶可以從指針釋放所有權,或者將所有權轉移到shared_ptr (具有用於此目的的構造函數 ),如果他們想要使用不同的所有權方案。

效用函數的最佳參數類型是什么?

引用,除非程序流程如此復雜,以致在函數調用期間對象可能被銷毀,在這種情況下為shared_ptrweak_ptr (在任何一種情況下,它都可以引用基類,如果需要,還可以添加const限定符。)

保持對象引用的函數的最佳參數類型是什么?

shared_ptrunique_ptr ,如果您希望它對對象的生命周期負責,而不是擔心它。 原始指針或引用,如果可以(簡單而可靠地)安排對象比使用它的所有內容更長。

我將以相反的順序回答,以便從簡單的案例開始。

實用程序函數接受來自工廠函數的對象,使用它們,但不取得所有權。 (例如,計算文檔中單詞數量的函數。)

如果要調用工廠函數,則始終通過工廠函數的定義獲取所創建對象的所有權。 我想你的意思是, 其他一些客戶首先從工廠獲得一個對象,然后希望將它傳遞給不依賴於所有權的效用函數。

在這種情況下,效用函數根本不關心如何管理它所操作的對象的所有權。 它應該簡單地接受一個(可能是const )引用或者 - 如果“沒有對象”是一個有效的條件 - 一個非擁有的原始指針。 這將最大限度地減少接口之間的耦合,並使實用程序功能最靈活。

在返回對象后保持對對象引用的函數(如獲取對象副本的UI組件,以便它可以根據需要在屏幕上繪制內容。)

這些應該按值獲取std::shared_ptr 這使得從函數的簽名中可以清楚地看出它們共享參數的所有權。

有時,擁有一個對其參數具有唯一所有權的函數(構造函數會浮現在腦海中)也是有意義的。 那些應該通過值 (或通過右值引用)獲取std::unique_ptr 這也將使簽名中的語義清晰。

一個工廠函數(在類之外),它創建對象並將它們返回給類的用戶。 (例如,打開文檔並返回可用於訪問內容的對象。)

這是困難的,因為std::unique_ptrstd::shared_ptr都有很好的參數。 唯一明確的是返回一個擁有的原始指針是不好的。

返回std::unique_ptr是輕量級的(與返回原始指針相比沒有開銷)並傳達工廠函數的正確語義。 調用該函數的人獲得對所制造對象的獨占所有權。 如果需要,客戶端可以以動態內存分配為代價從std::unique_ptr構造std::shared_ptr

另一方面,如果客戶端無論如何都需要std::shared_ptr ,那么讓工廠使用std::make_shared以避免額外的動態內存分配會更有效。 此外,在某些情況下,您必須使用std::shared_ptr ,例如,如果托管對象的析構函數是非virtual ,並且智能指針將轉換為指向基類的智能指針。 但是std::shared_ptrstd::unique_ptr有更多的開銷,所以如果后者足夠,我們寧願避免這種情況。

總而言之,我想出了以下准則:

  • 如果需要自定義刪除器,請返回std::shared_ptr
  • 另外,如果您認為大多數客戶無論如何都需要std::shared_ptr ,請利用std::make_shared的優化潛力。
  • 否則,返回一個std::unique_ptr

當然,您可以通過提供兩個工廠函數來避免此問題,一個返回std::unique_ptr ,另一個返回std::shared_ptr因此每個客戶端都可以使用最符合其需求的函數。 如果你經常需要這個,我猜你可以通過一些聰明的模板元編程來抽象大部分冗余。

大多數其他答案都涵蓋了這一點,但@TC與一些非常好的指導方針有關,我想在此總結一下:

工廠功能

生成引用類型的工廠應默認返回unique_ptr ,如果要與工廠共享所有權,則返回shared_ptr - GotW#90

正如其他人所指出的那樣,如果您願意,您作為unique_ptr的接收者可以將其轉換為shared_ptr

功能參數

除非您想使用或操縱智能指針本身,例如共享或轉移所有權,否則不要將智能指針作為函數參數傳遞。 首選按值, *&傳遞對象,而不是通過智能指針傳遞對象。 - GotW#91

這是因為當您通過智能指針時,您會在函數的開頭遞增引用計數器,並在結束時遞減它。 這些是原子操作,需要跨多個線程/處理器進行同步,因此在大量多線程代碼中,速度損失可能非常高。

當你在函數中時,對象不會消失,因為調用者仍然持有對它的引用(並且在函數返回之前不能對對象做任何事情 )所以如果你不是,那么遞增引用計數是沒有意義的在函數返回后保留對象的副本。

對於不取得對象所有權的函數

如果你需要表示null(沒有對象),請使用* ,否則更喜歡使用& ; 如果對象是僅輸入的,則編寫const widget*const widget& - GotW#91

這不會強制您的調用者使用特定的智能指針類型 - 任何智能指針都可以轉換為普通指針或引用。 因此,如果您的函數不需要保留對象的副本或取得對象的所有權,請使用原始指針。 如上所述,對象不會在函數中間消失,因為調用者仍然保持它(除非在特殊情況下,如果這對您來說是個問題,您已經知道了。)

對於確實取得對象所有權的函數

使用by-value unique_ptr參數表示“接收器”功能。

void f( unique_ptr<widget> );

- GotW#91

這使得該函數明確了該對象的所有權, 並且可以將原始指針傳遞給您可能具有遺留代碼的指針。

對於獲取對象共享所有權的函數

表示函數將使用by-value shared_ptr參數存儲和共享堆對象的所有權。 - GotW#91

我認為這些指南非常有用。 閱讀引號來自的頁面以獲得更多背景和深入解釋,這是值得的。

在大多數情況下,我會按值返回unique_ptr 不應該分享大多數資源,因為這使得很難對其生命周期進行推理。 您通常可以通過這種方式編寫代碼以避免共享所有權。 無論如何,你可以從unique_ptr創建一個shared_ptr ,所以它不像你限制你的選擇。

暫無
暫無

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

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