C++ Topic

up
  1. constは内部リンケージ
  2. C++でCのrealloc( )に相当する関数は?
  3. NULLより 0 の方がいい
  4. スマートポインタ
  5. 菱形多重継承の対処
  6. コピーコンストラクタとデフォルトコピーコンストラクタ
  7. Koenigルックアップ


  1. const は内部リンケージ (プロ言§9.2)
     constはデフォルトで内部リンケージだそうです。ですから、Cで言うと "static"が付与されているイメージになります。C++では static も少し別の 意味をもつことになりますので、混乱しないように。 ですから、下記2ソース中のiFooは衝突しません。OKということです。
    	---foo.cpp---
    	const int iFoo = 10;
    	...
    	
    	---bar.cpp---
    	const int iFoo = 20;
    	...	
    私がconstキーワードに興味を持ったのは、Stroustrupが「C++ではマクロは ほとんど全く不要である。」と言っており、その代替としてconstenumを使用することを推奨していたからです。
     同じく typedef もそうです。


  2. C++でCのrealloc( )に相当する関数は? (プロ言§1.6.1)
     Cではメモリの割り当て、解放は関数 malloc( )とfree( )。
     C++では演算子 new と delete を使用します。
    では、Cの realloc( )に相当する C++ の演算子または関数はあるのでしょうか? 答えは「なし」です。「renew」などと血迷ったことを言ってはいけません。 Stroustrupは、STLのvectorを 試してみろと言っています。私も推薦です。 STLを使い始めたら、決まりきったアルゴリズムやデータ構造はガリガリ書くのが 億劫になります。ま、必要がなくなるんです。


  3. NULLより 0 の方がいい (プロ言§5.1.1)
     有名なC言語FAQ日本語訳 の質問5.17に NULLポインタに 0以外が割り当てられてる機種があることを知って以来、 "NULL"と表記するように心掛けてきた私にとってはセンセーショナルでした。 0を推す理由です。 どうしても、NULL を使用したい場合は次のように定義しておく。
    		const int NULL = 0 ;
    	


  4. スマートポインタ (ARM§13.4.6)
     演算子のオーバーロード(overloading)で感心したものです。 演算子->をオーバーロードしてクラスメンバにアクセスするポインタ です。気が利き(smart)ます。
    	struct foo{ int x ; };
    
    	class bar{
    	    foo *pfoo;
    	public:
    	    bar( ):pfoo( 0 ){}
    	    ~bar( ){}
    	    foo* operator->( ){     // 単項演算子でここではポインタを返す
    	        return pfoo;
    	    }
    	};
    
    	void hogehoge( foo v, foo &r, foo *p ) {
    	    int i = v->x;        // OK. (v.operator->())->x と解釈
    	    i = r->x;            // OK. (r.operator->())->x と解釈
    	    i = p->m;            // NG. ポインタの間接参照. メンバなし
    	}
    	


  5. 菱形多重継承の対処
     VC++で線の描画プログラムを書いたのですが、MFCでは線幅が1以外の時は 実線以外の線種(点線、破線、一削点線、一削二点線など)が描画できません。 「MFCだから」と割り切り、クラスを自作しました。その時に菱形多重継承の 落とし穴に落ちてしましました。仮想基底クラスを知らなかったので ハマったのですが、基底クラスを継承するときの「virtual」一語で解決した 時には笑ってしまいました。  更に、オーバーライド(overriding)し、且つオーバーロード(overloading)した 描画関数の落とし穴にも落ちてしまいましたが、いい勉強になりました。 どうですか、検討がつきますか(笑)?
    そのソースを近くアップでもしようと思います。お楽しみに。


  6. コピーコンストラクタとデフォルトコピーコンストラクタ
     システムがデフォルトで用意しているコンストラクタを デフォルトコピーコンストラクタといいます。例えばオブジェクトの代入 (関数の値渡しも含む)の際には通常、デフォルトコピーコンストラクタが呼ばれます。 が、下記の例のようにメンバにポインタを含んでいる場合など、複製されたオブジェクト も同一ポインタを持つことから一方を解放した時、他方の整合性が取れませんし危険です。 この対策として、コンストラクタにこれを考慮しユーザーが定義できるものを 区別してコピーコンストラクタと呼んでいます。
    	class foo {
    	    char *p;
    	public:
    	    foo(): p(0) {}             // 通常のコンストラクタ
    	    foo( const foo &ob );      // コピーコンストラクタ( X-X-Ref )
    	    ~foo(){ delete [] p; }
    	    void set_p( char *pc ) {
    	        p = new char[ strlen( pc ) + 1 ];
    	        strcpy( p, pc );
    	    }
    	};
    
    	// コピーコンストラクタ
    	foo::foo( const foo &ob ) {
    	    p = new char[ strlen( ob.p ) + 1 ];
    	    strcpy( p, ob.p );              // 文字列コピー
    	}
    
    	int main( ) {
    	    foo ob1;
    	    ob1.set_p( "hogehoge" );
    
    	    foo ob2 = ob1;     // コピーコンストラクタが呼ばれます
    
    	    return 0;
    	}
    	
     コピーコンストラクタはオブジェクトのコピーの際に意味を持ちます。コピー コンストラクタの宣言はクラスXの場合、X( const X& ) と記述します。 これよりコピーコンストラクタは短縮して、`X-X-ref' とも呼ばれます。 サンプル
     オブジェクトのコピーは関数の引数関数の戻り値例外の3ケース存在します。ここで Stroustrup の言葉(プロ言§11.7)を引用します。
     「代入と初期設定は異なる演算だということはいくら強調しても し過ぎることはないほどだ。」

    *

     関連することなのでついでに、Andrew Koenig & Barbara E.Moo の 「Ruminations on C++」(邦訳:C++再考) から、引用します。

     もし、クラスのユーザがコピーを使うことを禁じたければ、コピーコンストラクタ (と、たぶん、代入演算子)をプライベートにすればよいのです。
    	class Thing {
    	public:
    	    // ...
    	private:
    	    Thing( const Thing & );
    	    Thing & operator=( const Thing & );
    	};
    


  7. Koenigルックアップ
     Argument-dependent name lookup とも言われています。 詳しくは標準C++の(3.4.2)を どうぞ。Koenigルックアップに関して記述している和書、邦訳洋書が非常に 少ないのが残念です(プロ言、ARM、更には Scott Meyersの `Effective'の2冊 にもありません)。  下記の例ですと、main()の中の g()の引数のt1の名前空間Nより、g()の 名前空間を同様にNとルックアップしたことになります。次の演算子+も 同様です。単純に考えると、冗長にスコープ解決演算子を付与して、N::g( N::t1 ) と 記述しなくてはスコープが解決しません。これを便利に考慮してくれたのが、 Andrew Koenigです。感謝..
    	#include <iostream>
    
    	namespace N {
    	    struct T {
    	        // body
    	    };
    	    void g( T& ) {
    	        std::cout << "N::g()" << std::endl;
    	    }
    	    T operator+( T x, T y ) {
    	        std::cout << "N::operator+()" << std::endl;
    	        return y;
    	    }
    	};
    
    	int main( )
    	{
    	    N::T    t1,t2;
    	    g( t1 );            // t1が N:: なので N::g() ルックアップ
    	    t1 + t2;            // t1,t2が N:: なので N::operator+() ルックアップ
    	    return 0;
    	}
    	
    [要注意]
     Microsoft VC++6.0(SP3)では、上記の関数g()のルックアップに失敗します。 (コンパルエラーで"error C2065: 'g' : 定義されていない識別子です。"と出力されます) これはMicrosfot報告済みのバグです。SP4がリリースされましたが、まだ確認 してません。どうだか..  やっぱりダメだった(-_-;2000/07/19
     ちなみに OS:Win95、Cygwin B20 の g++ (egcs-2.91.57)では、コンパイルできます。 また、LASER5 Linux 6.2 の egcs-2.91.66 でも確認しました(当然OK)。Boland C++ 5.5.1 もOK。


[注]
プロ言:「プログラミング言語C++ 第3版」Bjarne Stroustrup
ARM:「注解C++リファレンス・マニュアル」M.A.Ells & B.Struostrup


by masu
e-mail: massun.masumoto@nifty.ne.jp
URL : http://member.nifty.ne.jp/~masumoto/