2014-02-25

【C++】初心者でも簡単にクラスを扱える『スマートポインタ』

no_image.jpg

[ PR ]


シンプルで分かりやすい『C』、何でもできるが複雑な『C++』

まず最初に、私はJavaと関数型言語ばかり使ってきたので、あまりC++は得意ではありません。

ただ、CとC++は似て非なるものです。

というのも、C++が勝手にCも書けるようにしただけであって、本来は全くの別物だからです。

私はC はスマートでシンプルなので大好きですが、C++ は機能が多すぎて混沌としており、長らく毛嫌いしてきました。

最終的に避けられないC++

ios-android-cpp.png

しかしながら、C++は長年のデファクトスタンダードだったことは事実であり、OSを含めて多くの資産が存在します。

また、iPhone/iPadとAndroidの唯一の架け橋がC++でもあるため、再度人気を得ているという背景もあります。

そのため、ますますC++の重要性が増しており、ここにきてようやく重い腰を上げました。

スクリプト言語からも導入を楽にする『スマートポインタ』

ただ、いきなりC++を勉強するとなると、大きな壁となるのがやはりポインタですよね。

CでもC++でもポインタがあるせいで、なんと9割の学生が挫折すると定評があります。笑

そんな難しそうなポインタですが、C++では以下のような感じになります。

#include <iostream>

using namespace std;

class A {
public:
    A(){
        cout << "A" << endl;
    }

    ~A(){
        cout << "~A" << endl;
    }
};

class B {
public:
    B(){
        cout << "B" << endl;
    }

    ~B(){
        cout << "~B" << endl;
    }
};

int main(){
    A* a = new A();
    B* b = new B();

    delete a;
    delete b;

    // A
    // B
    // ~A
    // ~B
}

いきなりうわっとするかもしれませんが、ポイント以下だけです。

  • AとBというクラスがある
  • Aを初期化すると "A" と表示される
  • Bを初期化すると "B" と表示される
  • Aを削除すると "~A" と表示される
  • Bを削除すると "~B" と表示される

これを踏まえると確かにその通りになっています。

「ポインタを手動で管理する ≒ メモリを手動で管理する」 といえるので、メモリ管理とよくいわれます。

C++では、オブジェクトは自動で掃除(削除)されず、手動で行わなければなりません。

RubyやJavaScriptを普段使っていると、めんどくさいなぁと思うかもしれませんが、これを乗り越えないとC++ができません。困ったものです。

Sharedポインタ

そんなおっくうなメモリ管理を自動で行ってくれるのが『Sharedポインタ』です。

使ってみると簡単にわかります。

#include <iostream>
#include <boost/shared_ptr.hpp>

using namespace std;

class A {
public:
    A(){
        cout << "A" << endl;
    }

    ~A(){
        cout << "~A" << endl;
    }
};

class B {
public:
    B(){
        cout << "B" << endl;
    }

    ~B(){
        cout << "~B" << endl;
    }
};

int main(){
    boost::shared_ptr<A> a(new A);
    boost::shared_ptr<B> b(new B);

    // A
    // B
    // ~B
    // ~A
}

今度はdeleteを書いていないので「~A ~B」は表示されないはずですが、ちゃんと表示されています。

表示されているということは、ちゃんと削除されているということですね。

このように、スマートポインタを使うとスクリプト言語のような楽チンさに少し近づくことができます。

Sharedポインタは万能ではない

しかし、必ず全ての場合に使えるかというとそうではありません。

#include <iostream>
#include <boost/shared_ptr.hpp>

using namespace std;

class Sample {
public:
    Sample(){
        cout << "Sample" << endl;
    }

    ~Sample(){
        cout << "~Sample" << endl;
    }

    boost::shared_ptr<Sample> ptr;
};

typedef boost::shared_ptr<Sample> SampleSp;

int main(){
    SampleSp p1(new Sample);
    SampleSp p2(new Sample);

    p1->ptr = p2;
    p2->ptr = p1;

    // Sample
    // Sample
}

あれ、ちゃんと削除されていませんね。困りました。

ちなみにソース中のSampleSpというのは、Sharedポインタを見やすく短縮したものです。

実は、今度のケースでは以下の特徴があります。

  • Sampleというクラスがある
  • Sampleクラスは、Sampleクラスの子を1つ持っている
  • p1, p2はSampleクラスの実体である
  • p1はp2を、p2はp1をお互いに子に持っている

このようなケースを、相互参照といいます。

相互参照は特殊なケースで、厄介なことが起こります。

相互参照には『Weakポインタ』を使う

実は、スマートポインタは参照カウントというものを使っていつ削除するか決めています。

直感的な説明

あまり厳密ではありませんが、以下のように説明することができます。

例えば以下の場合はSの参照数は4なので削除されません。

ptr_ref_4.png

しかし以下の場合はSの参照数は0なので自動的に削除されます。

ptr_ref_0.png

例えば先程のAとBの例では、以下のようにAとBの参照数は0なので自動的に削除されます。

ptr_a_b_0.png

しかし、上手くいかなかった例では、以下のように2つの手がつながっている形になり、AとBの参照数は2なので削除されないことになってしまいます。

ptr_a_b_double.png

こういった問題を解消するために、弱参照といわれる弱い参照を持つWeakポインタを使うことでうまく削除することができます。

Weakポインタに置き換える

なんとなく理解したところで、先程の例に早速Weakポインタを使ってみます。

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

using namespace std;

class Sample {
public:
    Sample(){
        cout << "Sample" << endl;
    }

    ~Sample(){
        cout << "~Sample" << endl;
    }

    boost::weak_ptr<Sample> ptr;
};

typedef boost::shared_ptr<Sample> SampleSp;

int main(){
    SampleSp p1(new Sample);
    SampleSp p2(new Sample);

    p1->ptr = p2;
    p2->ptr = p1;

    // Sample
    // Sample
    // ~Sample
    // ~Sample
}

今度はちゃんと削除出来ましたね。

Sampleをshared_ptr、子の方はweak_ptrにすることで、子は相互参照しても削除されるからです。

ちょっと難しいですが、以下の図のような相互参照循環参照を扱う場合は注意をしてください。

ptr_a_b_each.png

ptr_a_b_c_each.png

C++11では標準サポート

実は、こっそりBoostという外部のライブラリを使って説明していましたが、C++11ではmemoryというライブラリで標準サポートされるようになったので安心してください。

#include <iostream>
#include <memory>

class Sample
{
public:
    Sample() {}
    ~Sample() {}

    std::weak_ptr<Sample> ptr;
};

int main()
{
    std::shared_ptr<Sample> p1(new Sample);
    std::shared_ptr<Sample> p2(new Sample);

    p1->ptr = p2;
    p2->ptr = p1;

    return 0;
}

C++11以前の方は、Boostを使ってくださいね。

まとめ

C++はかなり難しい部類に入るプログラミング言語ですが、スマートポインタが導入されることで少し楽になるのではないかと思います。

新しくラムダ式なども導入されるので、どんどん便利になりますね。

やさしいC++ 第4版 (「やさしい」シリーズ)
高橋 麻奈
ソフトバンククリエイティブ
売り上げランキング: 91,694

コメントはTwitterアカウントにお願いします。

RECENT POSTS


[ PR ]

.