2013年11月25日月曜日

添字演算子のオーバーロードはconstと非const両方実装しよう

添字演算子 (subscript operator) をオーバーロードする時、constを付けないとconst呼び出しができなくなります。

非constで実装

class Foo {
    int arr[6];
public:
    Foo &operator [] (int i) {return arr[i];}
};
int main() {
    const Foo f_c;
    int v = f_c[0]; // ここでエラー
    Foo f;
    f[0] = 1;
}

逆に、constを付けると添字演算子を使用した要素の変更ができなくなってしまいます。

constで実装

class Foo {
    int arr[6];
public:
    const Foo &operator [] (int i) const {return arr[i];}
};
int main() {
    const Foo f_c;
    int v = f_c[0];
    Foo f;
    f[0] = 1; // 今度はここでエラー
}

こういう時にconstメンバ関数と非constメンバ関数のオーバーロードをします。

class Foo {
    int arr[6];
public:
    Foo &operator [] (int i) {return arr[i];} // 非constバージョン
    const Foo &operator [] (int i) const {return arr[i];} // constバージョン
};
int main() {
    const Foo f_c;
    int v = f_c[0]; // const関数が呼ばれる
    Foo f;
    f[0] = 1; // 非const関数が呼ばれる
}
このように両方実装することで、コンパイラが自動的にconstメンバ関数と非constメンバ関数のどちらか適切な方を呼び出してくれます。 (参考サイト: http://www.geocities.jp/ky_webid/cpp/language/020.html)

2013年11月5日火曜日

Effective C++ 読書会メモ Item 2

Effective C++ 読書会のメモです。原書を元に独自の付け足しも行っているため誤りが含まれている可能性があります。

Item2: Prefer consts, enums, and inlines to #defines.

「プリプロセッサよりコンパイラを使う方が良い」と言い換えても良い

理由: #defineは本来的に言語の一部として扱われないため

たとえば、

#define ASPECT_RATIO 1.653
のASPECT_RATIOというシンボルはコンパイラには見えない。(事前にプリプロセッサが数値を埋め込んでしまうため。) 従って、シンボルテーブルに入らない。 この場合、この定数に関連するコンパイルエラーが発生した場合はASPECT_RATIOは表示されず、1.653という数値のみが表示される。 シンボリックデバッガを使用するときも同様に"ASPECT_RATIO"は表示されない。 もしこの定数が外部のヘッダファイルに定義されている場合はこの定数がどこに由来するものなのか探すのが大変になる。

解決法

const double AspectRatio = 1.653;

全て大文字の表記はマクロ(#define)由来のものを通常指すので、constは一部のみ大文字にしている。 浮動小数点定数を使う場合は、コード量が小さくなるという利点もある。 (#defineを使った場合は数値がすべての箇所に埋め込まれてしまうため。)

ポインタのconstには2種類あることに注意

const char * authorName = "Scott Meyers"; // ポインタの指す先の値が定数
char const * authorName = "Scott Meyers"; // 同上
char * const authorName = "Scott Meyers"; // ポインタ自身(アドレス)が定数
const char * const authorName = "Scott Meyers"; // 両方定数
char const * const authorName = "Scott Meyers"; // 同上

しかし文字列の場合はchar*よりもstd::stringを使うほうが良い

const std::string authorName("Scott Meyers");

クラス内定数

class GamePlayer {
private:
    static const int NumTurns = 5; // constant declaration
    int scores[NumTurns]; // use of constant
    ...
};
ソース内で実体を常に1つだけにする(オブジェクトごとに生成されないようにする)ためにはstaticをつける 上記はdeclaration(宣言)であることに注意。 定義はクラスの外に書く。 const int GamePlayer::NumTurns; ただし、組み込み型の定数で、かつ定数のアドレスを使用しない場合は省略できる

これの詳細を調べるため実験。 ヘッダファイル"test.h"

#include <iostream>

class GamePlayer {
public:
    static const int NumTurns = 5; // constant declaration
private:
    int scores[NumTurns]; // use of constant
public:
    GamePlayer(int n = 0) {std::fill_n(scores, NumTurns, n);}
    void print() const {
        std::cout << "NumTurns = " << NumTurns << std::endl;
    }
    void printAddress() const {
        std::cout << "Address of NumTurns = " << &NumTurns << std::endl;
    }
    void printScores() const {
        for(int i=0; i<NumTurns; ++i) std::cout << scores[i] << " ";
        std::cout << std::endl;
    }
};

// printAddress()を呼び出すにはこれが必要。そうでなければ不要
const int GamePlayer::NumTurns;

class Game {
public:
    static const GamePlayer player;
public:
    void print() const {player.print();}
    void printScores() const {player.printScores();}
};

// クラス内定数がユーザー定義クラスオブジェクトの場合は
// 定義時にコンストラクタ引数を与える
const GamePlayer Game::player(7);
ソースファイル
#include "test.h"

int main(int argc, char** argv) {
    GamePlayer player;
    player.print();
    player.printAddress();
    Game game;
    Game::player.printScores();
}
実行結果
NumTurns = 5
Address of NumTurns = 0x8048918
7 7 7 7 7

当然だが、#defineにはスコープが無いのでクラス内定数は実現できない。 つまり、定数のカプセル化ができない。

*古いコンパイラの場合
クラス内で

static const int NumTurns = 5;
として初期値を与えるとコンパイルエラーになる。 その場合は以下のように記述する。
class CostEstimate {
private:
    static const double FudgeFactor; // declaration of static class
    ...
};
const double CostEstimate::FudgeFactor = 1.35; // definition of static class
しかし、配列で定数を使用する場合は下記のように書くとエラーになる。
class GamePlayer {
private:
    static const int NumTurns; // constant declaration
    int scores[NumTurns]; // use of constant
    ...
};
const int GamePlayer::NumTunrs = 5;
このような場合はenumハックを使用する。
class GamePlayer {
private:
    enum { NumTurns = 5 }; // “the enum hack” — makes
    // NumTurns a symbolic name for 5
    int scores[NumTurns]; // fine
    ...
};
enumハックには以下の理由で知っておく価値がある。
  • constはアドレス(ポインタと参照)を取れるがenumは取れない
  • enumはメモリを確保しないことが保証されている
(const定数もアドレスを使用しなければメモリは確保されないが、しょぼいコンパイラの場合はメモリを確保することがあり得る) ちなみにenumハックはテンプレートメタプログラミングの基本テクでもある。

#defineの悪い点に戻る。 下記マクロは難点が多く、有害である。

// call f with the maximum of a and b
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
マクロは引数にカッコをつけなければ式を代入した場合に演算順序が狂って正常に動かない上、 下記コードは意図に反する動作をする。
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a is incremented twice
CALL_WITH_MAX(++a, b+10); // a is incremented once
このようなナンセンスなマクロは、下記インライン関数を用意すれば使わずに済む。
template<typename T> // because we don’t
inline void callWithMax(const T& a, const T& b)  // know what T is, we
{
    f(a > b ? a : b); //  pass by reference-to-const - see Item 20
}
インライン関数は関数であり、スコープやアクセスルールに従うのでカプセル化が可能。 インライン関数を使えばもはや(あの忌まわしき)マクロに関わることなんてなくなる。

以上のように、const, enum, inlineを使えばプリプロセッサの使用を減らせる。 しかし、なくなるわけではない。 #includeは必要だし、#ifdef/#ifndefはコンパイルを制御する重要な役割を持ち続けるだろう。

Thints to Remember

  • 単純な定数には#defineよりもconstオブジェクトやenumを使うのが良い
  • 関数ライクなマクロについては、#defineよりもインライン関数を使うのが良い

Effective C++ 読書会メモ Item 1

Effective C++ 読書会のメモです。原書を使用しているため誤訳があるかもしれません。

Item1. View C++ as a federation of languages

C++は最初、Cにオブジェクト指向を加えただけの言語"C with Classes"だった。 しかしException、template、STLが加わって派手で大胆な言語に変化した。

C++は手続き型、オブジェクト指向、関数型、generic、メタプログラミングをサポートするmultiparadigm言語 このような機能と柔軟性においてC++は他に類を見ない言語だが、筋の通った書き方をするのは難しい。 最も簡単な方法は、C++を言語の複合体として見ること。 特定のサブ言語に限定すれば、構造はシンプルで覚えやすい。 C++は以下4つのサブ言語から成り立っていることを意識しよう。

1. C
C++はCを基礎としている。ブロックや文、組み込み型、配列、ポインタなどはCから来ている。 しかし、C++は同等の機能をよりスマートに実現できる。 具体例: Item2(プリプロセッサの代替)やItem13(オブジェクトによるリソース管理)

2. オブジェクト指向C++
元来の"C with Classes"そのもの。 クラス・カプセル化・継承・ポリモーフィズム・仮想関数など。

3. テンプレートC++
C++のジェネリックプログラミングを実現する構成要素であり、おそらくほとんどのプログラマにとって最も馴染みの薄い機能。 テンプレートはとても強力で、テンプレートメタプログラミング (TMP)という完全に新しいパラダイムを生み出した。 テンプレートプログラミング専用の節も用意している。(Item46やItem48)

4. STL
特別なテンプレートライブラリで、コンテナ、イテレータ、algorithm、関数オブジェクトが美しく調和している。 STLを利用する時は、STLの作法に沿うことを求められる。

以上が4つのサブ言語。 あるサブ言語から別のサブ言語に切り替える時に効果的なプログラミングが変化しても驚かないで欲しい。 たとえばC言語の組み込み型では値渡しが参照渡しよりも高速だが、C言語からオブジェクト指向C++に移った時、ユーザー定義のコンストラクタとデストラクタが存在するオブジェクトはたいていconst参照渡しが値渡しよりも速くなる。また、このことはテンプレートC++を使った時に顕著になる。どんな型のオブジェクトが引数で渡されるかわからないからだ。しかしSTLを使うとき、イテレータや関数オブジェクトは再び値渡しが高速になる。

このように、C++は一つのルールに基づく統一された言語ではなくサブ言語の複合体で、それぞれが独自の規約を持つ。 このことを理解すれば、C++をより簡単に理解できるようになるはずだ。

Things to Remember

効果的なC++プログラムの書き方はC++のどの部分を使うかによって変化する。

2013年10月14日月曜日

テンプレートクラスの静的メンバ変数の使用法

クラス内で(アドレスを使用しない組み込み型以外の)静的なメンバ変数を定義した場合、クラス外でも宣言をしないと使用できません。(リンク時にエラーとなります。) (参考サイト: http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02028.html

テンプレートクラスでも同様で、以下のように書けば問題ありません。

template<int N1, int N2>
class MyClass {
private:
    static int* p;
};
template<int N1, int N2>
int* MyClass<N1, N2>::p;

なお、コンパイル確認はMicrosoft Visual C++ Compiler Nov 2012 CTP (v120_CTP_Nov2012)で行いました。

2013年10月6日日曜日

ポインタを格納したvectorの重複要素をメモリリークせずに削除する方法

C++で、deleteを自動で行うuniqueを実装することでstd::vectorの重複要素が指すメモリ領域を解放する方法です。

vector<MyClass*> arr に対して重複した要素を以下のように単にuniqueとeraseを使用して削除すると、vectorから削除された重複要素のメモリが解放されません。

std::sort(arr.begin(), arr.end(), my_smaller);
auto end_it = unique(arr.begin(), arr.end());
fields.erase(end_it, arr.end());
fields.shrink_to_fit(); 
// ここで重複要素(ポインタ)のメモリは解放されますが、
// そのポインタが指すメモリ領域は解放されません

vector<shared_ptr<MyClass>> arr としても上記やり方ではメモリが解放されませんでした。 (なお、sortでは代入操作が行われるためvector<unique_ptr<MyClass>> arr は使用できません。)

そこで、重複要素をvectorから削除する際に重複要素の指すメモリを解放するuniqueを実装することにします。 uniqueの実装は下記リンクに書かれているので、これを改変してp_uniqueを実装しました。 http://www.cplusplus.com/reference/algorithm/unique/

template <typename T, typename ForwardIterator>
ForwardIterator p_unique (ForwardIterator first, ForwardIterator last)
{
    if (first==last) return last;
    ForwardIterator result = first;
    while (++first != last) {
        // ここで重複要素のメモリを解放する
        if(**result == **first) delete (T*)*first;
        else *(++result)=*first;
    }
    return ++result;
}

これを使用すると、下記のようにポインタを格納したvectorをソートして重複したオブジェクトを削除し、不要になったメモリを解放することができます。

vector<MyClass*> arr;
[arrにデータを入れる処理]
std::sort(arr.begin(), arr.end(), my_smaller);
auto end_it = p_unique<MyClass>(arr.begin(), arr.end());
fields.erase(end_it, arr.end());
fields.shrink_to_fit();

my_smallerはポインタの値でなくそれが指すオブジェクトの値で比較するための関数です。

bool my_smaller(const MyClass *e1, const MyClass *e2)
{
    return *e1 < *e2;
}

また、sortとuniqueを使用するには、MyClassで以下の2つの演算子をオーバーロードしておく必要があります。

bool operator < (const MyClass &e);
bool operator == (const MyClass &e);