クラスメンバーのスマートポインターの使用


159

C ++ 11のクラスメンバーとしてのスマートポインターの使用法を理解できません。私はスマートポインターについて多くを読みましたが、私はどのようにunique_ptr、およびshared_ptr/またはweak_ptr一般的に機能するかを理解していると思います。わからないのは本当の使い方です。誰もunique_ptrがほとんどいつも行く方法として使うことを勧めているようです。しかし、私はこのようなものをどのように実装しますか:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

ポインタをスマートポインタに置き換えたいとしましょう。のunique_ptrためにA は機能しないでしょうgetDevice()?だから、私が使用した時間だshared_ptrweak_ptr?使用方法はありませんunique_ptrか?shared_ptr本当に小さなスコープでポインターを使用していない限り、ほとんどの場合、私には思えるのですが?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

それは行く方法ですか?どうもありがとう!


4
これは、存続期間、所有権、およびヌルの可能性について本当に明確にするのに役立ちます。たとえばdevice、のコンストラクタに渡された後もsettings、それを呼び出しスコープで参照できるようにしsettingsますか、それとも経由してのみ参照できますか?後者の場合、unique_ptr便利です。また、あなたはの戻り値がシナリオなければならないのgetDevice()ですがnull。そうでない場合は、参照を返します。
キース

2
はい、shared_ptr8/10の場合はa が正しいです。他の2/10はとの間unique_ptrで分割されweak_ptrます。また、weak_ptr通常は循環参照を解除するために使用されます。あなたの使用法が正しいと考えられるかどうかはわかりません。
Collin Dauphinee 2013年

2
まず、deviceデータメンバーにどのような所有権を求めますか?最初にそれを決定する必要があります。
juanchopanza 2013年

1
わかりました。呼び出し側として、unique_ptr代わりにa を使用して、コンストラクターを呼び出すときに所有権を放棄できることを理解しています。しかし、Settingsクラスの設計者として、呼び出し元も参照を保持したいかどうかはわかりません。多分デバイスは多くの場所で使用されます。OK、多分それはまさにあなたのポイントです。その場合、私は唯一の所有者ではないので、そのときにshared_ptrを使用すると思います。そして:スマートポイントはポインターを置き換えますが、参照を置き換えませんか?
michaelk 2013年

this-> device = device; 初期化リストも使用します。
ニルス

回答:


202

unique_ptrためにA は機能しないでしょうgetDevice()

いいえ、必ずしもそうとは限りません。ここで重要なことは、オブジェクトの適切な所有権ポリシーを決定することです。Deviceつまり、(スマート)ポインターが指すオブジェクトの所有者になる人を決定します。

Settingsオブジェクトだけのインスタンスになるのでしょうか?ウィルDeviceオブジェクトは自動的に破棄されなければならないSettingsオブジェクトが破棄されます、またはそれは、そのオブジェクトよりも長生きする必要がありますか?

最初のケースでstd::unique_ptrは、それが必要なものですSettings。これは、ポイントされたオブジェクトの唯一の(一意の)所有者と、その破壊を担当する唯一のオブジェクトを作成するためです。

この仮定の下でgetDevice()は、単純な監視ポインタを返す必要があります(監視ポインタは、ポイントされたオブジェクトを存続させないポインタです)。最も単純な監視ポインタは、生のポインタです。

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[ 注1: ここで生のポインタを使用しているのはなぜでしょうか?実は、それは貴重な警告ですが、正しいコンテキストでそれを置くことが重要である:生のポインタが悪い手動メモリ管理を行うために使用された場合、すなわち割り当てと割り当て解除のオブジェクトを経由newしてdelete。参照セマンティクスを実現し、所有していない監視ポインターを渡すための純粋な手段として使用する場合、未処理ポインターには、ぶら下がりポインターを逆参照しないように注意する必要があるという事実を除いて、本質的に危険なものはありません。- END注1 ]

[ 注2: コメントで明らかになったように、所有権が一意であり、所有オブジェクトが常に存在することが保証されている(つまり、内部データメンバーdeviceが存在することは決してないnullptr)この特定のケースでは、関数getDevice()は可能性がありますポインタではなく参照を返します。これは本当ですが、私はここで生のポインタを返すことにしました。これは、できる可能性がある場合に一般化できる短い答えでdeviceありnullptr、生のポインタがそれらを使用しない限りOKであることを示すためです手動メモリ管理。- END注2 ]


もちろん、Settingsオブジェクトがデバイスの排他的な所有権を持つべきではない場合、状況は根本的に異なります。これは、たとえば、Settingsオブジェクトの破棄が、指定されたDeviceオブジェクトの破棄も同様に意味しない場合に当てはまります。

これは、プログラムの設計者であるあなただけが知ることができるものです。あなたが提供する例から、私がこれが事実であるかどうかを判断するのは難しいです。

あなたはそれを理解しやすくするために、あなたは別に、他のオブジェクトがあるかどうかを自問もSettings維持する権利を有していることDeviceだけではなく、受動的なオブザーバーであることの、長い彼らはそれへのポインタを保持するようとして生きているオブジェクトが。それは確かにそうである場合は、必要な共有の所有権政策何で、std::shared_ptrオファーを:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

これweak_ptr監視ポインタであり、所有ポインタではないことに注意してください。つまり、ポイントされたオブジェクトへの他のすべての所有ポインタがスコープ外に出た場合、ポイントされたオブジェクトは存続しません。

weak_ptr通常のrawポインタよりも優れている点weak_ptrは、ぶら下がっているかどうか(つまり、有効なオブジェクトを指しているのか、それとも最初に指し示していたオブジェクトが破棄されているのか)を安全に判別できることです。これはexpired()weak_ptrオブジェクトのメンバー関数を呼び出すことで実行できます。


4
@LKK:はい、正解です。A weak_ptrは常に生の監視ポインタの代替手段です。逆参照する前にぶら下がっているかどうかを確認できるため、ある意味で安全ですが、オーバーヘッドも伴います。ダングリングポインターを逆参照しないことを簡単に保証できる場合は、生のポインターを監視することで問題ありません
Andy Prowl

6
最初のケースでは、おそらくgetDevice()リファレンスを返すようにした方がいいでしょうね?したがって、呼び出し元はを確認する必要はありませんnullptr
vobject 2013年

5
@chico:どういう意味かわかりません。呼び出されたauto myDevice = settings.getDevice()タイプの新しいインスタンスを作成し、返される参照によって参照さDeviceれるインスタンスmyDeviceからそれをコピー構築しgetDevice()ます。myDevice参考になりたい場合は、行う必要がありますauto& myDevice = settings.getDevice()。つまり、何かが足りないのでなければ、を使用しなかったときと同じ状況に戻りますauto
Andy Prowl 2013年

2
@Purrformance:オブジェクトの所有権を譲渡したくないため-変更可能オブジェクトをunique_ptrクライアントに渡すと、クライアントがオブジェクトから移動する可能性が開かれるため、所有権が取得され、ヌル(一意)ポインターが残ります。
Andy Prowl 2014年

7
@パフォーマンス:クライアントが移動するのを防ぐことができます(クライアントがconst_castsに熱心な科学者でない限り)、私は個人的にはしません。これは、実装の詳細、つまり所有権が一意であり、を通じて実現されるという事実を公開しunique_ptrます。私はこのように見ています。所有権を渡す/返す必要がある場合は、スマートポインタ(unique_ptrまたはshared_ptr、所有権の種類に応じて)を渡す/返す必要があります。所有権を渡したり戻したりしたくない場合は、(適切にconst修飾された)ポインタまたは参照を使用します。これは、ほとんどの場合、引数をnullにできるかどうかに依存します。
Andy Prowl 2014年

0
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptr参照ループにのみ使用されます。依存関係グラフは非循環有向グラフでなければなりません。共有ポインターには、2つの参照カウントがあります。1つはshared_ptrs、1つはすべてのポインター(shared_ptrおよびweak_ptr)です。すべてshared_ptrのが削除されると、ポインタが削除されます。ポインタがから必要とされている場合weak_ptrlockそれが存在する場合は、ポインタを取得するために使用されなければなりません。


だから私があなたの答えを正しく理解していれば、スマートポインタは生のポインタを置き換えますが、必ずしも参照ではありませんか?
マイケルク2013年

実際には2つの参照カウントがありshared_ptrますか?理由を教えてください。私が理解している限り、オブジェクトを操作するときにweak_ptr新しいものを作成するだけなので、カウントする必要はありませんshared_ptr(基になるオブジェクトがまだ存在する場合)。
ビョルンPollex

@BjörnPollex:短い例を作成しました:リンク。コピーコンストラクタとだけをすべて実装したわけではありませんlockブーストバージョンは、参照カウント時にスレッドセーフでもあります(delete1回だけ呼び出されます)。
Naszta 2013年

@Naszta:あなたの例は、2つの参照カウントを使用してこれを実装することが可能であることを示していますが、あなたの答えは、これが必要であると示唆していますが、これはそうではありません。回答でこれを明確にしていただけませんか?
ビョルンPollex

1
@BjörnPollexはweak_ptr::lock()、オブジェクトの有効期限が切れているかどうかを判断するために、最初の参照カウントとオブジェクトへのポインタを含む「制御ブロック」を検査する必要があるため、weak_ptrまだ使用中のオブジェクトがある間は制御ブロックを破棄してはなりません。したがって、weak_ptrオブジェクトの数を追跡する必要があります。これは、2番目の参照カウントが行うことです。最初の参照カウントがゼロになるとオブジェクトが破壊され、2番目の参照カウントがゼロになると制御ブロックが破壊されます。
Jonathan Wakely 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.