Tips (1)

とくに一本の記事にまとめるほどのものでもないようなトピックを集めてみた。 C++ の中・上級者 (あるいはマニア) から見るとどれも常識に思えるものばかりだと思うが、 初めて知ったときには、私なりに多少なりとも驚きを覚えたものを揃えたつもりである。

なお、各項目の末尾に付けた [番号] は、 『プログラミング言語 C++ 第3版』において同項目の解説が書いてある節の番号である。


■ クラスの中にアクセスする必要がなければ、クラス名の宣言だけでよい

A をクラス名とする。ヘッダーファイル中で次のような関数宣言だけをしている場合には、 A の定義は不要であり、A がクラス名であるという宣言だけしてあればよい。

    class A;
    A foo( A a );    
というのも、コンパイラがここを解析しているときはまだ foo() の呼び出しは行われていないので、 A の内容を知る必要がないからである。A の定義が必要になるのは、 このヘッダーファイルをインクルードして、実際に関数 foo() を呼び出そうとする場合だ。 関数の宣言だけなら、その戻値や引数の型となっているクラスの定義は不要である。 ヘッダーファイルはできるかぎり依存関係を減らして簡潔に記述するようにしよう。


■ using-directive と using-declaration の違い [C.10.1]

using-directive の例は using namespace std; であり、 using-declaration の例は using std::cout; である。

using-directive はそれを宣言したスコープから、指定された名前空間内の名前に対して、 名前空間名による限定なしでアクセスを可能にするものである。 一方、using-declaration は、それを宣言したスコープに名前を追加する。

したがって、using-directive は何度でも行えるが、 同じ名前に対する using-declaration を2回以上行うことはできない (名前の多重宣言になってしまう)。またスコープ内にすでに存在する名前と同じものを using-declaration で追加することもできない。

  namespace N {
    int x, y;
  }

  void func() {
    float x;
    using N::x;          // エラー (x の2重宣言)
    using namespace N;
    x = 1.0;             // ローカルの x にアクセス
    N::x = 1;            // N::x にアクセス
    y = 2;               // N::y にアクセス
  }  

■ 基本型の明示的コンストラクタ [6.2.8]

intfloat などの組み込みデータ型も デフォルトコンストラクタとコピーコンストラクタを持つ。

  int i = int();       // i = 0;
  float f = float(i);  // f = 0.0;  

デフォルトコンストラクタ T() が生成する値は、型 T に対して static_cast<T>(0) である。 この機能があるので、テンプレートなどで型 T がユーザ定義型か否かを気にせずに T() という記述をすることが可能になる。

また、コピーコンストラクタは、型変換演算子として働く。


■ 何もデータを定義していないクラスでも、サイズは 1 バイト

次のような、メンバ変数を持たないクラス (構造体) でも、サイズは 0 にならない。

  class A { };  

sizeof( A ) は、1 になる。これは

    A a;
    A b;  

のように A のオブジェクトを生成したときに、 &a != &b となることを保証するためである。


■ const な参照は一時オブジェクトを束縛する [5.5] [10.4.10]

string s1string s2 があったとして、 s1 + s2 の結果は一時オブジェクトに格納され、普通は、 s1 + s2 を含む完全式 (代入文や関数呼び出しや if 文の条件式や for 文の制御式など) の実行が終了したところで破棄される。 しかし、下のように const なリファレンスによって束縛すると、 その寿命はリファレンスの寿命に合わせられる。

  {
    …
    const string& t = s1 + s2;
    …
    // s1 + s2 の寿命はここまで延ばされる
  }    

実は、上の代入式は、次のものとほとんど同値である。

  const string t = s1 + s2;     

戻値とコンストラクタの妖しい関係 を読んでもらえれば、その理由も分かるかと思う。


■ static const なメンバー変数は、整数型に限りクラス定義時に初期化できる [10.4.6.2]

整数系のデータ型なら、次のような初期化が可能であり、その値を他のところで使うこともできる。

  class A {
  public:
    static const int x = 10;   // 初期化可能
    char buf[ x ];             // 配列のサイズとして使用可能
  };  

double などの算術演算型についてはこのような初期化をすることはできない。

またコンパイラによっては、整数型であっても上のような初期化をすることができない場合がある。 そのようなときは、enum で代用するとよかろう。

  class A {
  public:
    enum { x = 10 };
    char buf[ x ];
  };  

■ ローカルクラスを使ったローカル関数の定義

C++ では、ローカル関数を定義できない。 つまり、関数スコープの中でさらに関数を定義することはできない。 しかし、関数スコープでも、クラス (ローカルクラス) は定義できる。 そのローカルクラスの中で static なメンバ関数を定義してやれば、 事実上、「ローカル関数」 と同じような使い方ができる。

次のように、std::for_each の第3引数として渡すこともできる。

  void foo( const vector& vi )
  {
    struct Local {
      static void square( int x ) { cout << x * x << endl; }
    };

    for_each( vi.begin(), vi.end(), Local::square );
  }  

operator-> の解釈 [11.10]

オブジェクト obj の属するクラスで operator-> が定義されていれば、 obj->member(obj.operator->())->member を表す。

operator-> が定義されていなければ、 obj->member(*obj).member を表す。

したがって、operaor-> の戻値は、ポインタか、 または operator-> が定義されているクラスのオブジェクトでなければならない。


■ friend なクラスの派生クラスは friend じゃない [C.11.4]

次のように class A に対して friend な class F があったとして、 その friend 関係は F の派生クラスである class DF には受け継がれない。

  class F;

  class A {
    friend class F;
  private:
      int x;
  };

  class F {
  public:
    int foo( A& a ) {
      return a.x;      // OK
    }
  };

  class DF : publid F {
  public:
    int bar( A& a ) {
      return a.x;      // NG
    }
  };  

その理由は、F が A の friend であることが分かっているとき、 F の派生クラスを作ることにより A の プライベートメンバにアクセスできてしまっては、 カプセル化が容易に破壊されることになるからである。


初出: 2002年7月15日

C++ ラビリンスの目次へ


ホームページへ / Last modified: 2002-12-17 16:04:31 JST
Created by OKA Toshiyuki < oka-t@fides.dti.ne.jp >