方法: shared_ptr インスタンスを作成して使用する

shared_ptr 型は、C++ 標準ライブラリのスマート ポインターで、複数の所有者がオブジェクトの有効期間を管理する必要があるシナリオ向けに設計されたものです。 shared_ptr を初期化した後、そのポインターをコピーすること、関数の引数内の値として渡すこと、および他の shared_ptr インスタンスに割り当てることができます。 すべてのインスタンスは同じオブジェクトを指し、1 つの "コントロール ブロック" へのアクセスを共有します。このコントロール ブロックは、新しい shared_ptr が追加されるとき、スコープ外になるとき、またはリセットされるときに必ず参照カウントをインクリメントおよびデクリメントします。 参照カウントが 0 に達したときに、コントロール ブロックはメモリ リソースと自らを削除します。

次の図に、1 つのメモリ位置を指す shared_ptr の複数のインスタンスを示します。

1 つのメモリ位置を指す 2 つの shared_ptr インスタンスを示す図。

最初の図は、MyClass インスタンスと ref count = 1 の制御ブロックを指す共有ポインター P1 を示しています。 2 番目の図は、別の共有ポインター P2 の追加を示しています。これも MyClass インスタンスと共有制御ブロックを指しており、ref count は 2 になっています。

セットアップの例

この後の例はここで示された必要なヘッダーをすべて含み、必要な型をすべて宣言していることを前提としています。

// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct MediaAsset
{
    virtual ~MediaAsset() = default; // make it polymorphic
};

struct Song : public MediaAsset
{
    std::wstring artist;
    std::wstring title;
    Song(const std::wstring& artist_, const std::wstring& title_) :
        artist{ artist_ }, title{ title_ } {}
};

struct Photo : public MediaAsset
{
    std::wstring date;
    std::wstring location;
    std::wstring subject;
    Photo(
        const std::wstring& date_,
        const std::wstring& location_,
        const std::wstring& subject_) :
        date{ date_ }, location{ location_ }, subject{ subject_ } {}
};

using namespace std;

int main()
{
    // The examples go here, in order:
    // Example 1
    // Example 2
    // Example 3
    // Example 4
    // Example 6
}

例 1

最初にメモリ リソースを作成するときは、可能な限り、shared_ptr を作成するために make_shared 関数を使用してください。 make_shared は例外セーフです。 これは、コントロール ブロックとリソースにメモリを割り当てるために同じ呼び出しを使用し、その結果、構造のオーバーヘッドが削減されます。 make_shared を使用しない場合は、オブジェクトを shared_ptr コンストラクターに渡す前にオブジェクトを作成するために、明示的な new 式を使用する必要があります。 次の例では、新しいオブジェクトと共に shared_ptr を宣言して初期化するさまざまな方法を示します。


// Use make_shared function when possible.
auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");

// Ok, but slightly less efficient. 
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));

// When initialization must be separate from declaration, e.g. class members, 
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");

例 2

次の例では、別の shared_ptr によって割り当てられたオブジェクトの共有所有権を取得する shared_ptr インスタンスを宣言して初期化する方法を示します。 sp2 が初期化された shared_ptrであることを想定します。

//Initialize with copy constructor. Increments ref count.
auto sp3(sp2);

//Initialize via assignment. Increments ref count.
auto sp4 = sp2;

//Initialize with nullptr. sp7 is empty.
shared_ptr<Song> sp7(nullptr);

// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);

例 3

shared_ptr は、要素をコピーするアルゴリズムを使用しているときに、C++ の標準ライブラリ コンテナー内でも役立ちます。 基になるメモリが、必要とされている間は有効であり、必要なくなった後は無効になることを理解している場合は、要素を shared_ptr 内でラップし、他のコンテナーにコピーすることができます。 次の例では、ベクター内の remove_copy_if インスタンスに対して shared_ptr アルゴリズムを使用する方法を示します。

vector<shared_ptr<Song>> v {
  make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
  make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
  make_shared<Song>(L"Thalía", L"Entre El Mar y Una Estrella")
};

vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s) 
{
    return s->artist.compare(L"Bob Dylan") == 0;
});

for (const auto& s : v2)
{
    wcout << s->artist << L":" << s->title << endl;
}

例 4

dynamic_pointer_cast, static_pointer_cast および const_pointer_cast を使用して、shared_ptr をキャストすることができます。 これらの関数は、dynamic_caststatic_cast、および const_cast の各演算子に似ています。 次の例では、基底クラスの shared_ptr のベクトルにある各要素の派生型をテストしてから、要素をコピーし、それらに関する情報を表示する方法を示します。

vector<shared_ptr<MediaAsset>> assets {
  make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
  make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
  make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};

vector<shared_ptr<MediaAsset>> photos;

copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
    // Use dynamic_pointer_cast to test whether
    // element is a shared_ptr<Photo>.
    shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
    return temp.get() != nullptr;
});

for (const auto&  p : photos)
{
    // We know that the photos vector contains only 
    // shared_ptr<Photo> objects, so use static_cast.
    wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}

例 5

次の方法で、shared_ptr を別の関数に渡すことができます。

  • 値渡しで shared_ptr を渡します。 これは、コピー コンストラクターを呼び出し、参照カウントをインクリメントし、呼び出し先を所有者にします。 この操作には小規模なオーバーヘッドがあり、渡そうとする shared_ptr オブジェクトの数によっては、非常に大規模になる可能性があります。 呼び出し元と呼び出し先の間にあるコード コントラクト (暗黙的または明示的) が、呼び出し先を所有者にすることを要求する場合は、このオプションを使用します。

  • 参照または定数参照により、shared_ptr を渡します。 この場合、参照カウントはインクリメントされず、呼び出し元がスコープ内にとどまっている限り、呼び出し先はポインターにアクセスできます。 または、呼び出し先は参照に基づいて shared_ptr を作成し、その後、共有所有者になる方針を使用することもできます。 呼び出し元が呼び出し先を把握していない場合や、shared_ptr を渡す必要があり、パフォーマンス上の理由でコピー操作を回避したいと考える場合は、このオプションを使用します。

  • 基になるポインター、または基になるオブジェクトへの参照を渡します。 この結果、呼び出し先はオブジェクトを使用できますが、そのオブジェクトを有効にして所有権を共有することや、有効期間を延長することはできません。 呼び出し先が、生ポインターから shared_ptr を作成する場合は、新しい shared_ptr は元のポインターから独立し、基になるリソースを制御することはできません。 呼び出し元と呼び出し先の間のコントラクトが、呼び出し元が shared_ptr の有効期間の所有権を保持することを明示的に指定する場合は、このオプションを使用します。

  • shared_ptr を渡す方法を決定するときに、呼び出し先が、基になるリソースの所有権を共有する必要があるかどうかを判断してください。 "所有者" とは、基になるリソースを自らが必要とする限り、そのリソースを維持することができるオブジェクトまたは関数です。 呼び出し先が、(関数の) 有効期間を上回ってポインターの有効期間を延長できるようにすることを、呼び出し元が保証する必要がある場合は、最初のオプションを使用します。 呼び出し先が有効期間を延長するかどうかが問題にならない場合は、参照渡しを使用し、コピーするかどうかを呼び出し先に任せます。

  • ヘルパー関数に基となるポインターへのアクセスを与える必要があり、ヘルパー関数がそのポインターを使って、呼び出し元の関数が制御を返す前に戻ってくることがわかっている場合、その関数は基となるポインターの所有権を共有する必要はありません。 ヘルパー関数は、呼び出し元の shared_ptr の有効期間内にのみ、ポインターにアクセスする必要があります。 この場合は、shared_ptr を参照渡しにするか、生ポインターを渡すか、基になるオブジェクトへの参照を渡す方法が安全です。 この方法で渡すと、パフォーマンスに関するある程度の利点が生じ、プログラミングの意図を示すのに役立つ可能性もあります。

  • 時には、たとえば std::vector<shared_ptr<T>> のように、各 shared_ptr をラムダ式の本体または名前付き関数オブジェクトに渡す必要が生じることがあります。 ラムダまたは関数がそのポインターを格納しない場合は、shared_ptr の参照渡しを行い、各要素に対してコピー コンストラクターが呼び出されることを防止します。

void use_shared_ptr_by_value(shared_ptr<int> sp);

void use_shared_ptr_by_reference(shared_ptr<int>& sp);
void use_shared_ptr_by_const_reference(const shared_ptr<int>& sp);

void use_raw_pointer(int* p);
void use_reference(int& r);

void test() {
    auto sp = make_shared<int>(5);

    // Pass the shared_ptr by value.
    // This invokes the copy constructor, increments the reference count, and makes the callee an owner.
    use_shared_ptr_by_value(sp);

    // Pass the shared_ptr by reference or const reference.
    // In this case, the reference count isn't incremented.
    use_shared_ptr_by_reference(sp);
    use_shared_ptr_by_const_reference(sp);

    // Pass the underlying pointer or a reference to the underlying object.
    use_raw_pointer(sp.get());
    use_reference(*sp);

    // Pass the shared_ptr by value.
    // This invokes the move constructor, which doesn't increment the reference count
    // but in fact transfers ownership to the callee.
    use_shared_ptr_by_value(move(sp));
}

例 6

shared_ptr インスタンスによって所有されているメモリ上のポインター比較を有効にするために、shared_ptr がさまざまな比較演算子をオーバーロードする方法を次の例に示します。


// Initialize two separate raw pointers.
// Note that they contain the same values.
auto song1 = new Song(L"Village People", L"YMCA");
auto song2 = new Song(L"Village People", L"YMCA");

// Create two unrelated shared_ptrs.
shared_ptr<Song> p1(song1);    
shared_ptr<Song> p2(song2);

// Unrelated shared_ptrs are never equal.
wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;

// Related shared_ptr instances are always equal.
shared_ptr<Song> p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl; 

関連項目

スマート ポインター (Modern C++)