- 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++ではマクロは
ほとんど全く不要である。」と言っており、その代替としてconstや
enumを使用することを推奨していたからです。
同じく typedef もそうです。
- C++でCのrealloc( )に相当する関数は?
(プロ言§1.6.1)
Cではメモリの割り当て、解放は関数 malloc( )とfree( )。
C++では演算子 new と delete を使用します。
では、Cの realloc( )に相当する C++ の演算子または関数はあるのでしょうか?
答えは「なし」です。「renew」などと血迷ったことを言ってはいけません。
Stroustrupは、STLのvectorを
試してみろと言っています。私も推薦です。
STLを使い始めたら、決まりきったアルゴリズムやデータ構造はガリガリ書くのが
億劫になります。ま、必要がなくなるんです。
- NULLより 0 の方がいい
(プロ言§5.1.1)
有名なC言語FAQ日本語訳
の質問5.17に NULLポインタに 0以外が割り当てられてる機種があることを知って以来、
"NULL"と表記するように心掛けてきた私にとってはセンセーショナルでした。
0を推す理由です。
- 0(ゼロ)はint型なのでコンテキストによって標準変換で適切な
型に変換される。
- C++になって型チェックが厳しくなったので、どのNULLマクロよりも
ただの0(ゼロ)を使用する方が問題が少ない。
どうしても、NULL を使用したい場合は次のように定義しておく。
const int NULL = 0 ;
- スマートポインタ
(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. ポインタの間接参照. メンバなし
}
- 菱形多重継承の対処
VC++で線の描画プログラムを書いたのですが、MFCでは線幅が1以外の時は
実線以外の線種(点線、破線、一削点線、一削二点線など)が描画できません。
「MFCだから」と割り切り、クラスを自作しました。その時に菱形多重継承の
落とし穴に落ちてしましました。仮想基底クラスを知らなかったので
ハマったのですが、基底クラスを継承するときの「virtual」一語で解決した
時には笑ってしまいました。
更に、オーバーライド(overriding)し、且つオーバーロード(overloading)した
描画関数の落とし穴にも落ちてしまいましたが、いい勉強になりました。
どうですか、検討がつきますか(笑)?
そのソースを近くアップでもしようと思います。お楽しみに。
- コピーコンストラクタとデフォルトコピーコンストラクタ
システムがデフォルトで用意しているコンストラクタを
デフォルトコピーコンストラクタといいます。例えばオブジェクトの代入
(関数の値渡しも含む)の際には通常、デフォルトコピーコンストラクタが呼ばれます。
が、下記の例のようにメンバにポインタを含んでいる場合など、複製されたオブジェクト
も同一ポインタを持つことから一方を解放した時、他方の整合性が取れませんし危険です。
この対策として、コンストラクタにこれを考慮しユーザーが定義できるものを
区別してコピーコンストラクタと呼んでいます。
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 & );
};
- 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。