web-dev-qa-db-ja.com

ファクトリ関数に最適なスマートポインターの戻り値の型は何ですか?

スマートポインターと新しい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;
}
38
Malvineous

単純なケースから始めるために、逆の順序で答えます。

ファクトリー関数からオブジェクトを受け入れ、それらを使用するが、所有権を取得しないユーティリティー関数。 (たとえば、ドキュメント内の単語の数をカウントする関数。)

ファクトリー関数を呼び出す場合、ファクトリー関数の定義そのものによって、作成されたオブジェクトの所有権を常に取得します。あなたが言っているのは、いくつかのotherクライアントが最初にファクトリからオブジェクトを取得し、次に所有権を取得しないユーティリティ関数に渡したいと思うことです自体。

この場合、ユーティリティ関数は、操作対象のオブジェクトの所有権がどのように管理されるかをまったく気にするべきではありません。 (おそらくconst)参照を受け入れるか、「オブジェクトなし」が有効な条件である場合は、所有していないrawポインターを受け入れる必要があります。これにより、インターフェイス間の結合が最小限に抑えられ、ユーティリティ機能が最も柔軟になります。

戻った後、オブジェクトへの参照を保持する関数(必要に応じて画面にコンテンツを描画できるようにオブジェクトのコピーを取得するUIコンポーネントなど)

これらは std::shared_ptrby valueを取る必要があります。これにより、関数の署名から、引数の所有権を共有していることが明確になります。

時には、引数の一意の所有権を取得する関数を持つことも意味があります(コンストラクターが思い浮かびます)。これらは std::unique_ptrby value(または右辺値参照)を取る必要があります。これにより、署名からセマンティクスが明確になります。

オブジェクトを作成してクラスのユーザーに返すファクトリ関数(クラス外)。 (たとえば、ドキュメントを開いて、コンテンツにアクセスするために使用できるオブジェクトを返します。)

std::unique_ptrstd::shared_ptrの両方に適切な引数があるため、これは難しい問題です。唯一明確なことは、所有する生のポインタを返すことは良くないということです。

std::unique_ptrを返すことは軽量で(生のポインターを返すことに比べてオーバーヘッドがありません)、ファクトリー関数の正しいセマンティクスを伝えます。関数を呼び出した人は、作成されたオブジェクトの排他的所有権を取得します。必要に応じて、クライアントは動的メモリ割り当てを犠牲にしてstd::shared_ptrからstd::unique_ptrを構築できます。

一方、クライアントがstd::shared_ptrを必要とする場合、追加の動的メモリ割り当てを避けるために、ファクトリで std::make_shared を使用する方が効率的です。また、管理対象オブジェクトのデストラクタがvirtual以外で、スマートポインタが基本クラスへのスマートポインタに変換される場合など、単にstd::shared_ptrを使用する必要がある状況もあります。ただし、std::shared_ptrstd::unique_ptrよりもオーバーヘッドが大きいため、後者で十分な場合は、可能であればそれを避けます。

結論として、私は次のガイドラインを思いつきました。

  • カスタム削除機能が必要な場合は、std::shared_ptrを返します。
  • それ以外の場合、クライアントのほとんどがstd::shared_ptrを必要とすると思うなら、std::make_sharedの最適化の可能性を利用してください。
  • それ以外の場合は、std::unique_ptrを返します。

もちろん、twoファクトリー関数を提供することで問題を回避できます。1つはstd::unique_ptrを返し、もう1つはstd::shared_ptrを返すため、各クライアントが最適なものを使用できますそのニーズに合っています。これが頻繁に必要な場合は、巧妙なテンプレートメタプログラミングを使用して冗長性のほとんどを抽象化できると思います。

24
5gon12eder

ファクトリ関数に最適な戻り値の型は何ですか?

unique_ptrが最適です。偶発的なリークを防ぎ、ユーザーはポインタから所有権を解放したり、所有権をshared_ptr(その目的のために constructor があります)、異なる所有権スキームを使用したい場合。

ユーティリティ機能に最適なパラメーターの種類は何ですか?

参照。プログラム呼び出しが複雑で、関数呼び出し中にオブジェクトが破壊される可能性がある場合を除きます。その場合、shared_ptrまたはweak_ptr。 (どちらの場合でも、基本クラスを参照し、必要に応じてconst修飾子を追加できます。)

オブジェクトへの参照を保持する関数の最適なパラメータータイプは何ですか?

shared_ptrまたはunique_ptr、オブジェクトの存続期間に責任を負わせ、それ以外は心配しないようにする場合。生のポインターまたは参照。オブジェクトを使用するすべてのものよりも長く存続するように(単純かつ確実に)配置できる場合。

24
Mike Seymour

他の回答のほとんどはこれをカバーしていますが、@ T.C。ここに要約したいいくつかの本当に良いガイドラインにリンクしています:

ファクトリー関数

参照型を生成するファクトリは、デフォルトで_unique_ptr_を返すか、所有権をファクトリと共有する場合は_shared_ptr_を返す必要があります。 - GotW#9

他の人が指摘したように、_unique_ptr_の受信者は、必要に応じて_shared_ptr_に変換できます。

関数パラメーター

所有権の共有や譲渡など、スマートポインター自体を使用または操作する場合を除き、スマートポインターを関数パラメーターとして渡さないでください。スマートポインターではなく、値、_*_、または_&_でオブジェクトを渡すことをお勧めします。 - GotW#91

これは、スマートポインターで渡すとき、関数の開始時に参照カウンターをインクリメントし、終了時にデクリメントするためです。これらはアトミック操作であり、複数のスレッド/プロセッサ間での同期が必要なため、非常にマルチスレッド化されたコードでは速度のペナルティが非常に高くなる可能性があります。

関数内にいるとき、呼び出し元はまだオブジェクトへの参照を保持しているため(そして 関数が返るまでオブジェクトに対して何もできません )、オブジェクトは消えませんので、参照カウントをインクリメントします関数が戻った後にオブジェクトのコピーを保持しない場合、意味がありません。

オブジェクトの所有権を取得しない関数の場合

Null(オブジェクトなし)を表現する必要がある場合は_*_を使用し、そうでない場合は_&_を使用することをお勧めします。オブジェクトが入力専用の場合は、_const widget*_または_const widget&_と記述します。 - GotW#91

これにより、呼び出し元に特定のスマートポインタータイプの使用を強制することはありません。スマートポインターは通常のポインターまたは参照に変換できます。したがって、関数がオブジェクトのコピーを保持したり、オブジェクトの所有権を取得する必要がない場合は、生のポインターを使用します。上記のように、呼び出し側がオブジェクトを保持しているため、オブジェクトは関数の途中で消えることはありません(特別な状況を除き、これが問題になっている場合は既に認識しています)。

オブジェクトの所有権を取得する関数

By-value _unique_ptr_パラメーターを使用して「シンク」関数を表現します。

void f( unique_ptr<widget> );

- GotW#91

これにより、関数がオブジェクトの所有権を取得することが明確になり、およびレガシーコードから持っている可能性のある生のポインタを渡すことができます。

オブジェクトの共有所有権を取る関数の場合

値による_shared_ptr_パラメーターを使用して、関数がヒープオブジェクトの所有権を保存および共有することを表現します。 - GotW#91

これらのガイドラインは非常に役立つと思います。より多くの背景と詳細な説明のために引用が出されたページを読んでください、それは価値があります。

10
Malvineous