<< 前 ホーム 次 >>

bakaid: 20061111

恥ずかしながら、つい最近まで、C/C++の基本的な
カプセル化のテクニックを知りませんでした。

下にコードをあげておきますけど、このテクニックを
使うことで、外部から構造体のメンバに直接アクセス
できなくなるんですね。

それと、このテクニック、メンバに直接アクセスできなく
なるっていう情報隠蔽の効果だけじゃなくって、
ファイルの依存関係を減らすっていう効果もあるんです
よね。下のコードだとわかりにくいでしょうけど、C++の
クラス定義ヘッダだと効果絶大です。

でも、このテクニックも万能じゃなくって、使う側では
ポインタか参照でないと構造体を使えませんから、
スタックによる自動解放が期待できません。C++だったら
スマート・ポインタで負担は軽減できるでしょうけど。

ちなみに、このテクニックはincomplete classって
呼ばれるみたいですね。stdio.hのFILEがその代表例だとか。

http://www.boost.org/libs/smart_ptr/sp_techniques.html

/*
 * counter.c
 */
#include <stdlib.h>

struct counter {
    int count;
};

struct counter *
new_counter(void)
{
    return calloc(1, sizeof(struct counter));
}

int
get_count(struct counter *self)
{
    return self->count;
}

void
count_up(struct counter *self)
{
    self->count++;
}

/*
 * counter.h
 */
#ifndef counter_h_guard
#define counter_h_guard

struct counter;
struct counter *new_counter(void);
int get_count(struct counter *self);
void count_up(struct counter *self);

#endif /* counter_h_guard */

/*
 *main.c
 */
#include <stdio.h>
#include "counter.h"

int
main(void)
{
    struct counter *counter = NULL;

    counter = new_counter();
    printf("%d\n", get_count(counter));
    count_up(counter);
    printf("%d\n", get_count(counter));
    return 0;
}

--

他にもスマート・ポインタを使いたくなるのは、
クラスのメンバにvectorみたいなのがあるとき。要素の
管理がコピーで済むなら問題ないけど、そうじゃない
ときも結構あります。そうすると、デストラクタで
vectorをチマチマたどって1つずつdeleteしなきゃ
いけない。

#include <iostream>
#include <vector>

using namespace std;

class Foo {
private:
    int id;

public:
    Foo(int id) : id(id) {
        cout << "new " << id << endl;
    }

    virtual ~Foo() {
        cout << "delete " << id << endl;
    }

    virtual int getId() {
        return id;
    }
};

class Container {
private:
    vector<Foo*> foos;

public:
    ~Container() {
        for (vector<Foo*>::iterator i = foos.begin();
	     i != foos.end(); ++i) {
	    delete *i;
       }
    }

    void add_foo(int id) {
        foos.push_back(new Foo(id));
    }
};

int
main()
{
    Container container;
    container.add_foo(0);
    container.add_foo(1);
    container.add_foo(2);
    return 0;
}

こんなの面倒だからスマート・ポインタを使って
デストラクタを書かずに済ませたい。

というわけで、スマート・ポインタのお勉強。

Boostを使うのが定石なんでしょう。Boostのスマート・
ポインタにはいくつか種類があって、よくわからない
んですけど、shared_ptrを使っときゃいいみたい。

次のコードは、Boostのサンプルとほぼ同じなんですけど、
気にしない。変わるのはContainerだけ:

class Container {
private:
    vector<Foo*> foos;

public:
    ~Container() {
        for (vector<Foo*>::iterator i = foos.begin();
	     i != foos.end(); ++i) {
	    delete *i;
       }
    }

    void add_foo(int id) {
        foos.push_back(new Foo(id));
    }
};

--

ところで、ここでメンバfoosのぜんぶの要素を書き出す
メンバ関数を追加すると:

    void print_foos() {
        for (vector<shared_ptr<Foo> >::iterator i = foos.begin();
             i != foos.end(); ++i) {
            cout << (*i)->getId() << endl;
        }
    }

みたいな感じ。たかが配列たどるのに何文字タイプ
しなきゃなんないんだって話。こういうのはtypedef
するのが常識らしい:

class Container {
private:
    typedef shared_ptr<Foo> FooPtr;

    vector<shared_ptr<Foo> > foos;

public:
    void add_foo(int id) {
        shared_ptr<Foo> foo(new Foo(id));
        foos.push_back(foo);
    }

    void print_foos() {
        for (vector<FooPtr>::iterator i = foos.begin();
             i != foos.end(); ++i) {
            cout << (*i)->getId() << endl;
        }
    }
};

でも、typedefしたって、大してタイプ量が減らないように
思うのは自分だけ? こんなんだったらイテレータなんか
使いたくない。結局、正直者がバカを見るようじゃダメ
でしょ。まぁ、C++には型推論とか入るはずもないから、
正直者は報われないわな。

--

shared_ptrそのものがスタックから消えたときは、
もちろん、それが指してるオブジェクトのデストラクタが
走ります。じゃあ、指す先を変えたときは?

指す先を変えるにはresetを使います:

int
main()
{
    shared_ptr<Foo> p(new Foo(0));
    p.reset(new Foo(1));
    p.reset(new Foo(2));
    p.reset(new Foo(3));
    p.reset(new Foo(4));
    return 0;
}

Fooは前と同じだから省略。で、これの出力結果は:

new 0
new 1
delete 0
new 2
delete 1
new 3
delete 2
new 4
delete 3
delete 4

というわけで、resetするとデストラクタが走る。

本家Permlink

<< 前 ホーム 次 >>


Copyright © 1905 tko at jitu.org

バカが征く on Rails