\n
[ トップページ ]
[ ___ _CommandPrompt ]
[ ___ _JScript ]
[ ___ _MySQL ]
[ ___ _Cygwin ]
[ ___ _Java ]
[ ___ _Emacs ]
[ ___ _Make ]
[ ___ _Perl ]
[ ___ _Python ]
[ ___ _OpenGL ]
[ ___ _C# ]
[ ___ _StyleSheet ]
[ ___ _C++ ]
[ ___ _Winsock ]
[ ___ _Thread ]
[ ___ _VisualStudio ]
[ ___ _C ]
[ ___ _Win32API ]
[ ___ _Lua ]
[ ___ _PhotoShop ]
ヘッダ検索
■ STL
■ 共通
コンテナクラスも 参照渡し可能 ( object なので当然 )
void testFunc( vector< int> &list )
...;
}
関数ポインタを渡す際は static でない mem func は不可能
理由は obj がない状態では 関数のアドレスが決まらないから
Container の memory を確保するために, new が call される.
それを選択させるのが, Allocator
何か処理をさせる場合は, ctor, method 経由で渡すことが大事
■ コンテナクラス
DESC
他のオブジェクトの集まり( コレクション )のクラス
List, Set, Dictionary
■ コンテナにオブジェクトをいれる
コンテナにいれる物は オブジェクトか、そのポインタのどちらかになる。
また、中身をそろえる、そろえないで2種類できる。
Java のように共通の基底クラス Object から派生したクラスをすべて入れることもできるが
有効なのは、特定の基底クラスから実装した派生クラスのみをいれること。
WARNING
ポインタをコンテナにいれる場合は
複数のポインタからインスタンスが参照されることになる。
そのため、delete した後のインスタンスを参照する危険性がある。
対策として SharedPointer などが使える。
または注意深く、オブジェクトを管理する。
REFERENCE SharedPointer
■ vector
SYNTAX
void resize( size_type _Newsize )
vector< int> b(7); // constructor の引数として、配列の要素数をとる
vector< int> b(7, 5) // 5 で初期化
■ 追加
SYNTAX
iterator push_back( iterator )
■ 削除
SYNTAX
iterator erase( iterator )
DESC
iterator がさす要素を削除 && 配列を自動で詰めなおす && 次の iterator を返す
vector< int> a;
a.push_back(1);
a.push_back(2);
vector< int>::iterator it = a.begin();
// 先頭の要素を削除
a.erase( it );
// 先頭から2番目を削除
it++;
a.erase( it );
■ サイズ
SYNTAX
void resize( size_type nr )
DESC
要素数を変更する。
■ 挿入
SYNTAX
iterator insert( iterator, const T& )
: iterator がさす "要素の前" に挿入 && 挿入した要素を指す iterator をかえす.
[0, 1, 2, 3, 4, 5, 6, 7]
^
|
itr
itr = v.insert( itr, 8 );
[8, 0, 1, 2, 3, 4, 5, 6, 7]
^
|
■ 参照
SYNTAX
reference operator [](size_type n);
const_reference operator [](size_type n) const;
reference at(size_type n);
const_reference at(size_type n) const;
// コンテナの i 番目の要素を v という別名で参照.
int &i = a[i];
// 実は次のことと同じ. ( 参照を取得するから代入可能. )
i[0] = 10;
コンテナクラスの代入 OK らしい( 要素が正しく operator=() の定義が必要 )
erase() をしても vector::end() は不変
// WARNING : itr ++ 扱い
for ( ; itr != itrend; ) {
if ( itr->flag ) {
itr = pnt.erase( itr );
} else {
itr ++;
}
}
{
vector< vector< int> > foo;
vector< int> bar;
bar.push_back(1);
bar.push_back(2);
bar.push_back(3);
vector< int> a;
a.push_back(11);
a.push_back(12);
a.push_back(13);
a.push_back(bar);
a.push_back(a);
printf(foo[1][2]); // = 13
}
■ multimap
POINT
lower_bound();
Desc
指定した値 [ 以.上 ] の要素が最初に現れる位置を返す,
count();
// x をキーに持つ要素の数を返す。
size_type count(const Key &x) const;
■ map
キーと値の対応表を表現するときに使う。
POINT
検索 O( log N )なので、vector, list と比較すると高速に検索できる。
連想 container
map< key, val >
key で sort されて格納( name でも sort )
[KEY] = VAL; // xxx.ini
POINT
listXXX.insert( make_pair( oneObj, twoObj ) );
WARNING
iterator を移動しながらの 削除はできない
map< string, int> l;
l.insert( make_pair("a" , 10) );
l.insert( make_pair("b" , 11) );
for( map< string, int>::iterator i=0; i!=l.end(); i++ ){
l.erase( i ); // ERROR
}
■ 挿入
tbl.insert( make_pair(key,val) );
tbl[ key ] = val;
■ 削除
// iterator の指す要素を削除して次の要素をさす iterator を返す
iterator erase( iterataor i );
size_t erase( key );
// key を指定して削除
map< string, int> m;
unsigned int nr = m.erase("foo" );
nr = 0; ならば消せていない
■ 検索 参照
Tbl::iterator i;
i = tbl.find( 2 );
T &map::opeartor []( const Key & )
DESC
キーに対応する要素を参照
左辺値になる場合に、同じ値を持つ要素がないときは k をキーに持つ右辺値を挿入
// val に対して, DefaultCtor が起動するらしい.
val = tbl[ key ];
typedef map< int , string > List;
List tbl;
tbl[0] = "aaa" ;
tbl[1] = "bbb" ;
tbl[0] = "ccc" ;
List::iterator i = tbl.begin();
for( ;i != tbl.end(); i++ ){
printf( i->second.c_str() );
}
// "bbb"
printf( tbl[1].c_str() );
// ""
printf( tbl[3].c_str() );
■ list
STL のリストは双方向リスト。
POINT
追加、削除は O(1)
参照は O(N)
追加、削除は早いので、頻繁に追加、削除をする場合に使う。
ただしアクセスは配列のように O(1) でアクセスできない。
DESC
iterator insert( iterator, const T& ) : vector< T> 同様.
operator [] は不可
■ 追加
list< int > l;
l.push_back( 1 );
l.push_front( 2 );
rbegin()
sort( bool func(T &, T &) );
■ 削除
SYNTAX
void pop_back();
void pop_front();
iterator erase( iterator );
リストから削除するときは, イテレータの指す場所から削除する。
erase() は削除した次の要素を指すイテレータを返す。
list< int > l;
l.push_back( 1 );
l.push_front( 2 );
list< int >::iterator i = l.begin();
for ( ; i != l.end(); i++ ) {
if ( *i == 2 ) {
l.erase( i );
break;
}
}
WARNING
erase しながら イテレートをする時は erase で返す iterator を受け取ること。
削除すみのリスト要素に対して ++ をすることになるため。
for ( ; i != l.end(); ) {
printf( "%d\n" , *i );
if ( *i == 2 ) {
i = l.erase( i );
}
else {
i++;
}
}
pop_front(), pop_back() は要素を返さずに削除するだけ。
list< int > l;
l.pop_back();
l.pop_front();
// 値がほしければ 参照のメソッドをつかってコピーをとる。
int i = l.front();
l.pop_front();
■ 参照
SYNTAX
T &front()
const T &front() const
T &back()
const T &back() const
DESC
リスト内の要素の参照をかえす。
list< int > l;
l.push_back( 1 );
l.push_front( 2 );
// 2
const int &ci = l.front();
int &i = l.front();
i = 3;
{
// 3
const int &ci = l.front();
// 1
l.back();
}
■ ソート
// a < b < c ... //小さい順
bool cmp( int &a, int &b ){ return a < b };
list< int> li;
li.sort( cmp );
pop_front | pop_back ; 要素がなくなる ( list の中身がかわるよ )
remove( val ) : val と一致するすべての要素を削除. ( ptr OK )
Predicator( Yes/No 関数. ) を利用して, loop する.
bool update_and_delete(Object* p)
{
p->update();
if( p->isDelete() ) {
delete(p);
return true;
}
return false;
}
do {
// Predicator を渡すことで, list を前後にわける.
// [1][1][1]....[0][0]...[0]
// . ここを返す.
// そして erase する.
objs.erase( remove_if( objs.begin(), objs.end(), update_and_delete), objs.end() );
} while( !objs.empty() ); // リストが空っぽになったら終了
■ set
POINT
データの重複を許さない集合のこと。
重複をしないリストをつくるときに便利。
追加, 削除 O(logN)
検索 O(logN)
重複をしない単語リストをつくる
typedef set< String > StringSet;
StringSet a;
string word;
// 単語を登録する
while ( cin >> word ) {
a.insert( word );
}
■ コンテナへの操作
■ std::find
SYNTAX
find(
first, // 検索開始位置のイテレータ
end, // 終了位置のイテレータ
T &value // 検索する値
)
DESC
value と同一の値を指す iterator をかえす
end を指すイテレータを返す。
int i = 0;
vector< int >::iterator itr = std::find(
tbl.begin(),
tbl.end(), // 終了
i // 探す要素
);
■ std::count
SYNTAX
find(
first, // 検索開始位置のイテレータ
end, // 終了位置のイテレータ
T &value, // 検索する値
result
)
■ std::remove
SYNTAX
find(
first, // 検索開始位置のイテレータ
end, // 終了位置のイテレータ
T &value // 検索する値
)
DESC
指定した範囲で value と一致する値を削除する
vector< int > v( 10 );
std::fill( v.begin(), v.end(), 7 );
std::remove( v.begin(), v.end(), 7 );
■ std::fill
SYNTAX
find(
first, // 検索開始位置のイテレータ
end, // 終了位置のイテレータ
const T &value
)
vector< int > v( 10 );
std::fill( v.begin(), v.end(), 7 );
■ std::replace
SYNTAX
replace(
first, // 検索開始位置のイテレータ
end, // 終了位置のイテレータ
const T &src //
const T &dst //
)
DESC
first から end の範囲で src の値を dst に変換する。
vector< int > v( 10 );
std::fill( v.begin(), v.end(), 7 );
std::replace( v.begin(), v.end(), 7, 10 );
■ std::unique
SYNTAX
find(
first, // 検索開始位置のイテレータ
end // 終了位置のイテレータ
)
DESC
first, end までの区間で重複を取り除く。
WARNING
unique を実行するには、コンテナの要素は sort されている必要がある。
vector< int > v( 10 );
std::fill( v.begin(), v.end(), 7 );
// sort してから
std::sort( v.begin(), v.end() );
// 重複をとる
std::unique( v.begin(), v.end() );
■ std::sort
SYNTAX
find(
first, // 検索開始位置のイテレータ
end // 終了位置のイテレータ
)
DESC
first, end までの区間をソートする。
中身はクイックソート。
REFERENCE qsort
std::random_shuffle( v.begin(), v.end() );
std::sort( v.begin(), v.end() );
for( int i=0; i< v.size(); i++ ){
cout < < v[i] < < endl;
}
POINT
自作のクラスもソートできる。
この場合は < 演算子のインターフェイスを持つことが条件になる。
昇順( ちいさい順 )にならべるには、 A < B ときたら A が小さいときに true を返すようにする
class vec2
{
public :
bool operator < ( const vec2 &rhs ) {
bool ret = length() < rhs.length();
return ret;
}
float length() const {
return x*x + y*y;
}
vec2( float _x, float _y ) : x(_x), y(_y){}
void toString() {
printf("%f %f\n" , x, y );
}
private:
float x, y;
};
vec2 v0( 1, 2 );
vec2 v1( 2, 2 );
vec2 v2( 0, 2 );
std::vector< vec2> a;
a.push_back( v0 );
a.push_back( v1 );
a.push_back( v2 );
std::sort( a.begin(), a.end() );
for( int i=0; i< a.size(); i++ ){
a[i].toString();
}
■ std::binary_search
SYNTAX
bool binary_search(
first, // 検索開始位置のイテレータ
end, // 終了位置のイテレータ
const T &value // 検索する値
)
DESC
first, end までの区間で binary_search をして、その結果を返す。
REFERENCE bsearch
set< int > a;
bool ret = binary_search( a.begin(), a.end(), 1 );
■ random_shuffle
指定した配列をランダムに並びかえる。
const int N = 1000;
int i = 0;
int *a = new int[ N ];
for( i=0; i< N; i++ ){
a[i] = 0;
}
std::random_shuffle( a, a + N );
■ テンプレート(Template)
■ Template する場所を見抜く方法
[ 処理が同じ ]で [ Data 型が異なる ]ところ.
-> 逆に関数とは, 特定の処理をひとくくりにすること.
Instanciate した時点で既に, template ではないことに注目.
次のような記述もえきる。
struct Tbl
{
string name;
void *(*create)();
};
Tbl tbl[] = {
{
{"Chara" , create< Chara > },
}
値を交換する処理は、型が違うだけで処理は同じ。
オーバーロードした瞬間にテンプレートが利用できないか考えること。
コンパイラにやらせてしまう。
int swap( int &a, int &b ) {
int t = a;
a = b;
b = t;
}
float swap( float &a, float &b ) {
float t = a;
a = b;
b = t;
}
型をパラメータ化して関数のテンプレートをつくる。
template < typename T>
void swap( T& a, T& b )
{
T t = a;
a = b;
b = t;
}
swap(x, y); // コンパイラが自動で生成する。
swap< int>(x, y); // 型を明示する場合は int をつける。
■ 関数テンプレート(FunctionTemplate)
SYNTAX
[ class | typename ] keyword 利用する。
template < class type>
function-declarator
template < typename type>
function-declarator
template < class X1 , class X2>
void println(X1 var1 , X2 var2);
関数テンプレートには2種類の場所で定義できる。
// template 型であることの宣言
template< class T >
T myabs( T a ){
a = a>0 ? a : -a;
}
// 使用するときは
int b = -7;
int n = myabs( b );
// 明示する
int b = -7;
int n = myabs< int>( b );
PrmTpl が返値のみでも OK .
-> ただしどの型に対して Instanciate するべきか教える必要がある.
-> 関数の後に < >() とあったら, PrmTpl を 明示していると考えよう.
template < typename T >
T foo()
{
T data = 0;
return data;
}
Client::main() {
int i = func< int>();
}
WARNING
引数が複数ある場合は、同一の型がくるとしても template 名を分ける必要あり
template< class T1, class T2 >
T1 Add( T1 a, T2 b ){
return a+b;
}
int a = 5;
char b = 3;
int n = add( a, b );
■ クラステンプレート(ClassTemplate)
DESC
意味としては、クッキーの型だと思えばOK
素材( 型 )と処理を分離して考える
内部的な型だけを変換していると思えばOK , あくまでクラス定義しているにすぎない
次のように考えるとわかりやすい
template< XXX > を除去
2. XXX -> 具体的な型に変換
[ vector ] を例に考えてみる.
vector< myInt > foo; // int 型 vec ( 使用する際は, < 型> とする )
template < T>
class vector{
T foo;
}
// 変換後( こう見ればよい )
class vector{
int foo;
}
// 使用する際は
vector< int > aaa; // [ int型 vector クラスの aaa を生成した ] と考える
■ 型だけでなく 値もパラメータとして受け取れる
非型パラメータを指定する場合は型名は実際の型にする。
// 固定サイズの配列
template < typename T, int N>
class Array {
public:
void push_back( const T &data ) {
data[ p ] = data;
p ++;
}
Array() : p(0){}
private:
T data[N];
int p;
};
使うときは非型パラメータの部分は値を指定する。
// int 型 10 要素の固定配列
Array< int, 10> a;
a.push_back( 5 );
整数型・列挙型の値
オブジェクトへのポインタ・参照
関数へのポインタ・参照
メンバへのポインタ
POINT
template の型を固定することもできる
//
// unsigned にすることで,負数を避けることが可能.
// アラインメントをそろえる
template< unsigned int BASE, typename T >
T floor( T x )
{
return static_cast< T>( ((x) + ((BASE)-1)) & ~((BASE)-1) );
}
// 利用する時は
roundUp< 16>( 10 );
■ テンプレートパラメータにデフォルト値を設定する
デフォルト値を指定するには、デフォルト引数と同様に = TYPE とする。
STL クラスはアロケータのデフォルトとして std::allocator< T> をもつ。
template < class T, class Allocator = allocator< T> >
class vector
{
...
}
そのため利用するときは、要素型を指定するだけでよい
自作のアロケータがある場合は、指定することでメモリ処理をカスタマイズできる。
class MyAllocator {
};
vector< int, MyAllocator > v;
■ template 化するコンパイラの立場で考えてみる
template < class T> // 以下は汎用クラスだよ. ( 未知の型がくるよ. とりあえず T にするよ )
template < [typelist] [ , [ arglist]] > declaration
パラメータ化した汎用関数、または汎用クラスだよー と compiler に通知.
declaration : 関数またはクラスを宣言
typename nameType
未知の識別子の[ 型 ]だよ- とコンパイラに通知( class keyword でも OK )
■ 特殊化する
DESC
ある特定のテンプレート型の場合だけ特別仕様にする時につかう。
特定の template 引数用に定義すること.
-> 特殊化する前に、汎用 template を定義する必要あり
XXX 型の場合の特別バージョンという意味。
template< >
void func< XXX >{}
特化するには, 汎用のテンプレート( 定義 | 宣言 )が必要になる。
// 基本は CMP 単位で処理されて,
// あ ! foo という FunctionTemplate があるな...
template < typename T > T foo();
// これは float 型の場合に利用する関数だな...
template < >
float foo< float>()
{
float data = 0.0f;
printf("special" );
return data;
}
Client::main() {
// foo はたしか, FunctionTemplate で float 型の特殊定義があったな...
// よし今回はこれを使おう.
foo< float >();
}
引数の数は合わせる必要があるらしい.
宣言のみ && 特化の場合は CMP は通るが, LINK で落ちる.
ie. 特化は別 Module でしてもいいことになる.
-> 要は, FunctionTemplate の宣言は, パラメータ数のみが一致すれば CMP OK な汎用宣言だと考えよう.
template < typename T>
void copy(T* dst, const T* src, size_t size)
{
for(size_t i = 0; i < size; i++) {
dst[i] = src[i];
}
}
// 特殊化( 型を指定しない -> < > , 逆に template< > とある場合は, template 本体がある. )
// XXX の場合と言い換えよう
template < >
void copy< char>(char* dst, const char* src, size_t size)
{
memcpy(dst, src, size);
}
template < >
static inline ErrorCode DEPRECATED createInterface( pad **unknown, uint32_t no)
{
return interfacebase::impl::createInterfaceImpl(unknown, no);
}
// 部分特殊化
// 元となるテンプレート
template < typename T1, typename T2, int I>
class A { ... };
// 部分特殊化したもの
template < typename T, int I>
class A< T, T*, I> { ... };
template < typename T1, typename T2, int I>
class A< T1*, T2, I> { ... };
template < typename T>
class A< int, T*, 5> { ... };
■ template 全般
Class, 関数の定義をパラメータ化する ( Java にない便利機能 )
定義を抽象化( Grouping )する.
通常は. 宣言と定義を header にする。
宣言のみでは LINK できない。
コンパイル時にコードを生成するため、コンパイル単位で定義が必要になるため。
基本的に define 同様の置換がされるが,
PreProcessor ではなくコンパイラがコードを生成する
汎用 class( 型 )だけでなく, 固定型も使用可能.
= をつけると Default 設定. ( = とでてきたら Default で [い.く.つ] と考えること )
実際に使用して == ( PrmTpl )を与えて, 評価されるらしい.
// MethodTemplate も作成できる
class Foo {
public:
template< typename T >
void func( T i )
{
cout < < i < < endl;
}
};
foo.func( 10.0f );
foo.func( 10 );
foo.func( "10" );
FunctionTemplate と 通常関数が 同名ならば, 通常関数が優先される.
-> ただし TypeCheck して固定関数にあうものがなければ, FunctionTemplate から生成されるよ.
template< typename T >
void func( T i )
{
cout < < i < < endl;
}
// 固定関数.
void func( int i )
{
cout < < "fixfunc " < < i < < endl;
}
{
unsigned int i = 10;
func( i ); // FunctionTemplate がよばれる.
}
前方宣言も OK
bCls が ClassTemplate の場合, bCls の変数はクラスを明示しないとアクセスできない.
( 言語仕様. GCC は準拠. VS++ では無視. )
class Object {
// ctor という Method を template 化した
template< typename T >
Object( T ty ){ ... }
};
int i;
Object o( i );
template < class T, int N = 1> // Default 1 && Arg2 は int であると CMP に通知.
class Allocator {
}
-> FuncTemplate には利用できないらしい. ( なんで ? )
Template 内容に矛盾があれば, コンパイル時にエラーがおきる
// SmartPtr を 実装.
class Resource
{
public:
void use(){ /*参照カウンタ+1*/ }
void release(){ /*参照カウンタ-1と解放*/ }
};
template < class T>
class SmartPointer
{
T *m_pointer; // 本体.
public:
// INIT
SmartPointer(T *p)
:m_pointer(p) {
{
// 置換した状態で矛盾があれば CmpErr
m_pointer->use();
}
~SmartPointer() {
m_pointer->release();
}
}
■ template 具体例
DESC
setter, getter の template 化
// prop object
template < class T> // 以下は汎用クラスだよ. ( 未知の型がくるよ. とりあえず T という名前にするよ ^/ )
class typeProp : public absProp
{
public:
virtual eProp getType() const;
// accessor という処理は同じ.
vitual T getVal( base *p ) = 0;
vitual void setVal( base *p, T val ) = 0;
};
■ 色々実験してみる
Q: PrmTpl を引数にとらない関数は FuncTemplate にできるか ?
template< typename T >
void func()
{
T i = 20;
printf( i );
}
func< int>(); // OK : 文として矛盾が起きないなら問題なし.
func(); // ERR: TemplateParamter がわからない.
< T > と教えてあげる.
func< Foo >(); // ERR: ここで本当に instantiated しているらしい.
Foo i = 20; // ERR
func(); // ERROR: no matching function for call to `func()' func が具体化されていいない証拠.
■ refcnt : 参照カウンタ
DESC
object 自身が他から, 参照される個数をカウントする.
0 になれば, 不要ということで解放. -> new した時点で 1.
POINT
2. create() 作成 && release() 破棄
3. Model から参照されている際に、削除できないようにリソースという扱いにしている
( ie. new, delete < -> create(), release() なんだ )
4. mdl->release() は mdl というポインタ( 矢印 )から Object(Resource)を離すイメージ
// delete のかわり、[ 参.照.し.な.い.よ ] と宣言.
virtual void release(){
counter --;
if ( counter == 0 ){
delete this;
}
}
// これを呼ばないとだめなのが, メンドイ.
void use(){
counter ++;
}
// 作成時に cnt = 1
Resource() : m_counter(1), m_name(0) {
}
WARNING
obj が相互に 参照する場合は, 循環がおき, mem leak がおきるので注意
任意の数値を文字列に変換する関数テンプレート。
#include < iostream>
#include < string>
#include < sstream>
template< class T>
string numberToString(T n)
{
stringstream s;
s < < n;
return ss.str();
}
■ 名前空間(NameSpace)
単に宣言された領域
名前空間はシンボルのスコープ( 見える範囲 )を決定する。
名前空間は異なる file をまたがっていても良い
使いどころは
名前( 識別子, Symbol )を局所化すること
配置する領域を指定して衝突しないようにする
■ 名前空間にあるシンボルを指定する
名前空間の中になるシンボルを指定するには スコープ演算子 :: をつかう。
std::vector< int > a;
std::cout
MyLibrary::Test t;
名前空間はネストできるので、指定する場合もルートからたどる。
MyLibrary::Math::Vector v;
毎回長い名前を指定するのも手間なので using namespace でシンボルの検索する場所を指定する。
using namespace MyLibrary::Math;
Vector v;
using namespace std;
vector< int > a;
■ 名前空間の使いどころ
同じクラス名をつけたいが、他のライブラリ内の名前と重複しないようにしたいときに使う。
たとえば Manager という管理クラスを複数人で別モジュールごとにつくる場合に
他を気にせずに同名の名前をつかうことができる。
各モジュールをつくる側は自分の名前空間でのみ考えることができる。
ある部署で部長というと、その部署の部長を指すのと同じ。
namespace Sound {
class Manager {
};
}
namespace IO {
class Manager {
};
class Mouse {
// これは自分の今いる名前空間 IO の Manager を指すので局所的に名前を指定できる。
Manager m;
};
}
名前空間を使わない場合は長い名前をつけることになる
class IO_Manager {
};
class Sound_Manager {
};
逆にモジュールの利用者は注意が必要。
どちらの Manager か明示する必要がある。
Sound::Manager snd_manager;
IO::Manager io_manager;
■ 名前の検索する順番
POINT
名前空間が入れ子になった場合は次の順番で名前が検索される。
基本ルール
自分のいる名前空間から最初に検索する。
例外ルール
継承している基底クラスがあれば、その名前空間を優先する
class Child;
class Title : public Base::Child {
// 継承をしているクラスの名前が優先されるので, Child は Base::Child になる。
Child *c;
};
// 明示するには
::Child *c;
}
■ よく使う名前はネームスペースにいれる
Vector, Node といった名前(シンボル)はいかにもよく使われるのでネームスペースにいれてやる。
かぶりそうな名前を, NameSpace でくるんでやる。
namespace MyLib{
class Vector {
};
int func() {
// MyLib::Vector クラスのこと
Vector v;
}
}
POINT
主語::symbol として考えるとわかりやすい
犬::jump();
string 犬::name; // 犬の名前
新しい標準関数はstd名前空間におかれる
namespace tokyo
{
// tokyo の suzuki さん
char *suzuki;
void foo(void){
// some code ...
}
// namespace 内では , block{} と同じく
// namespace 内から検索される
{
// tokyo::suzuki が参照される
printf( "name %s" , suzuki );
// 外の suzuki を見たいなら :: と明示する
printf( "name %s" , ::suzuki );
}
}
指定した namespace を見えるようにする
using namespace tokyo; // これ以降は暗黙でtokyoを調べてくれる
{ おとなりの } さとうさん.
-> でも毎度指定するのはメンドイ.
-> なので, どこどこさんちの話ね.( using namespace おとなりの) と言おう
こうするのもありかも ?
// これは実装の方法 !. 使うときはどうだろう ?
namespace 3年 {
namespace B組 {
void Teacher::teach() {
///
cout < < "人という字は..." < < endl;
}
}
}
// Namespace にいれるには
namespace Test {
...
}
namespace Test {
void func(){ printf("func" ); }
}
// call する
Test::func();
std もこのとおりになっている
Namespace は [ まさに ] その空間を探す. メタファは電話番号の市外局番.
lass を利用すると自動で Namespace に収まる
長い Namespace は別名をつけることが可能. ( namespace の alias )
namespace sName = long-long-long-name;
using namespace sName;
using namespace XXX; と言うときは XXX が先にないと CMP に怒られる
namespace 内で class の宣言をした後に, 定義( 実装 )をする場合は namespace は不要らしい.
-> あっても問題はないらしい. -> なぜか ?
CMP からしてみれば, XXXさんち( using )を利用しているならば, わかるということかな ?
class 定義内の class 定義は, 親クラスの namespace に収まる.
class Foo
{
// 当然 public section にある必要がある.
public:
class Bar {
public:
Bar(){ printf("Foo::Bar ctor" ); }
} b;
};
class Client {
public:
void main() {
Foo::Bar b;
}
}
POINT
namespace xxx{} 内で宣言. 定義されたものは. xxx::YYY となる.
内部にいる限りは, local の YYY が参照される.
会社内で, さとうさんと言えば, 会社::さとう を指すという自然な流れ.
なければ, さらに外の Scope に検索が広がる.
// class なしでも利用可能.
namespace OTONARISAN {
void aruku(){...}
}
void main() {
OTORINASAN::aruku();
}
WARNING
次の case は ERR: ( using namespace は使いすぎは注意. )
namespace A組; // A 組のこと
namespace B組;
using A組::さとうさん; // これは通じない
void func() {
さとうさん s; // ERR: どっちの ?
s.walk();
}
POINT
基本ルールは 現在の namespace から検索される
例外があって, 基底クラスがあれば, そちらが優先
class 内での宣言は不可能
namespace は コンパイル後の symbol として修飾される
-> ?f@doodle@@YAXXZ ( ie. 名前空間は言語仕様 )
// Class の定義と宣言に関する考察.
class Foo {
public:
// これは #include するのも含めて, 定義が複数の CMP 単位. ( .cpp )にあっても問題なし.
// setPos() とかで普通に利用している.
void func() {
printf( "impl" );
}
};
// この記述は 1 箇所でないと, Symbol が多重定義といって怒られる.
Foo::func() {
printf("impl" );
}
class 内にあれば, inline ( 要請 )関数と同じ扱いになる. -> 埋め込むので当然定義が必要になる.
cpp に 定義したクラスを AnonymousNameSpace にいれる意味がわからない.
共通ヘッダ. に次の記述をした場合はどうなるか. ?
// common.h
static unsigned int gNr;
// foo.cpp
include "common.h"
gNr ++;
// bar.cpp
include "common.h"
gNr ++;
A:
Compiler Linker 共に問題なし.
#include は文字とおり,
PreProcessor による埋め込みにすぎないことを忘れないこと.
埋め込まれた後の file 単位を想像すればわかりやすい.
// foo.cpp // bar.cpp
static unsigned int gNr; static unsigned int gNr;
gNr ++; gNr ++;
-> ので gNr は 別の Memory 上の領域. ( && File 外からはアクセスできない. )
以上を踏まえて, AnonymousNamespace を考えると
// foo.h
AnonymousNamespace {
class Foo {
};
}
FooDummy は コンパイル単位でアクセス可能 && 別の種類のクラスになる.
■ クラスのネームスペース
クラスもネームスペースをつくる。
class Impl は各クラスのネームスペースに収まる。
だから同じ名前があってもいい。
{{{
class Array {
// Array クラスの Impl
class Impl;
Impl *impl;
};
class List {
// List クラスの Impl
class Impl;
Impl *impl;
};
}}}
■ 無名ネームスペース(AnonymousNamespace)
特定のシンボルをファイル外からアクセスできないようにするために使う。
無名とあるが実際はコンパイラが適当な名前をつける。
仕組みは
__hidden_name__:: という形で [ compile file 単位 ]で, 名前が割り当てられる。
そして using namespace __hidden_name__ とされるので内部リンケージとなる仕組み。
C でいうところの内部リンケージの代用。
void xxx::impl() と同じになる。
// 名前なしでシンボルを定義する。
namespace
{
void impl() {
printf( "call " );
}
}
// 同一ファイル内からはコールできる。
void func()
{
impl();
}
使用側は名前でシンボルを特定できないので, リンクできない
extern void func();
func();
// 非公開のシンボルは名前が分らないのでアクセスできない。
impl();
ヘッダファイルで次の宣言をしておくと、インクルード先のファイル単位で別のクラスが生成される。
// foo.h
namespace {
class Foo {
};
}
■ 前方宣言(ForwardDeclare)
クラスの型ではなく、クラス情報( シンボル )のみで済む場合に宣言することで
コンパイラに知らせる。
Bar 型のポインタのため、 Bar の定義(構造)は知らなくてもよい。
そこで、 class Bar; としてシンボルの存在だけを通知する。
class Bar;
class Test {
Bar *p;
};
namespace 内のクラスを宣言する。
namespace Foo{
class Bar;
}
■ FunctionObject(ファンクタ)
DESC
function(関数) object といわれるもので
Cでいうコールバック関数ポインタの働きをする
関数のように動作する Object のこと
演算子 overload のうち、() をoverload したもの
POINT
使いどころ
より優れたコールバックを記述すること.
C などの手続き型プログラミング言語にコールバックは関数へのポインタによって実現することができる
コールバックの内外に状態変数を渡すことが難しい
strategy デザインパターンの完全な具現化であり、抜き差し可能な振る舞いを促進するもの。
STL では Template による. Functor を多用している
operator=()
operator()()
// 関数 object
class Cout()
{
public:
void operator()( int num ){
cout < < num < < endl;
}
};
// Client
int main(){
Cout func;
func( 5 ); // call back を呼ぶ
}
// template 化する
template < class T >
class Cout()
{
public:
void operator()( T num ){ cout < < num < < endl; }
};
std::for_each( c.begin(), c.end(), Cout< float>() );
->
どんな std:cout() に対応できるものならば、すべて可能 ( Template の原則 . )
STL と組み合わせることで、より強力な tool になる
// cpp の callback との比較
int comp_func( int a, int b )
{
return a > b;
}
// functor
class compCls
{
public:
operator()( int a, int b ){
return a > b;
}
};
// 宣言
template < class compFunctor>
void sort( int *p, int nr, compFunctor c );
// usr
int main()
{
int data[] = {0, 1, 2};
compCls func; // 上記 cls を使用
sort( data, 3, func );
// 使用する際は
func(); とする ?
}
// 二つの要素の順序関係を定義するコールバック関数を用いて並べ替えを行うルーチンの例
/* Callback function */
int compare_function(int A, int B) {
return (A < B);
}
// C の並べ替え関数の定義
void sort_ints(int* begin_items, int num_items, int (*cmpfunc)(int, int) );
int main() {
int items[] = {4, 3, 1, 2};
sort_ints(items, sizeof(items)/sizeof(int), compare_function);
}
// C++ では通常の関数の代わりに、operator() メンバー関数を定義して 関数呼び出し演算子をオーバーロードすることで、
// 関数オブジェクトを利用できる
class compare_class {
public:
bool operator()(int A, int B) {
return (A < B);
}
};
// C++ の並べ替え関数の定義 -> template になっている.
template < class ComparisonFunctor>
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);
int main() {
int items[] = {4, 3, 1, 2};
compare_class functor;
// PtrFunc ではなく, Object をわたす.
// Object なので, VarMem, FuncMem にアクセス可能.
sort_ints(items, sizeof(items)/sizeof(int), functor);
}
// Functor を別の用途で使ってみる.
functor_class Y;
int result = Y( a, b );
// Inline 化できるため, C の PtrFunc よりも性能がよい.
struct IncrementFunctor {
void operator()(int&i) { ++i; }
};
// 通常の関数による Increment
void increment_function(int&i) { ++i; }
template< typename InputIterator, typename Function >
Function for_each(InputIterator first, InputIterator last, Function f) {
for ( ; first != last; ++first) {
f(*first);
}
return f;
}
int A[] = {1, 4, 2, 8, 5, 7};
const int N = sizeof(A) / sizeof(a[0]);
for_each(A, A + N, IncrementFunctor());
for_each(A, A + N, increment_function);
// 状態も保持できる.
#include < iostream>
#include < iterator>
#include < algorithm>
// 要はクラス名が関数名になる.
class countfrom
{
private:
// 状態.
int count;
public:
countfrom(int n) : count(n) {}
int operator()() { return count++; }
};
int main() {
std::generate_n(std::ostream_iterator< int>(std::cout, "\n" ), 11, countfrom(10));
return 0;
}
■ const
■ const
定義
定数であることをコンパイラに宣言する。( ANSI C 規格 )
変数の前に記述する修飾子、その変数が定数であることを宣言する
文字列リテラルは、静的な領域に確保
静的な領域は Read Write, Read Only の領域に分かれる
文字列リテラルの領域に書き込みをすると、
segmentation fault を起こして処理が終了
// コンストなオブジェクトは初期化の時点のみ値を指定できる。
const a=6;
// コンパイラがエラーを検出してくれる。
a++;
ポインタの const は宣言する場所で意味がかわる。
// pが指す内容を変更できないp自体は変更可能
const char *p="abc" ;
char const *p="abc" ;
// p自体の変更は不可pが指す内容を変更可能
char * const p="abc" ;
■ const 記憶クラス
DESC
コンパイラに変数の変更をしないことを宣言する。
目的は、オブジェクトを間違って変更しないようにチェックしてもらうこと。
POINT
コンパイルの時点でのチェックなので、実行時のコストは0になる。
プログラムの安全性を高めるために積極的に使うといい。
指定したオブジェクトに変更をしないつもりならば const 宣言をする。
これによって不意に変更をしたときにコンパイラが怒ってくれる。
// path オブジェクトは変更しない意図であることを宣言する。
writefile( const string &path ) {
// ERROR
path = "" ;
}
WARNING
const 表明は早期にすること。
他のメソッドに連鎖的に影響をするため。
■ mutable(ミュータブル)
DESC
const method に対して, 例外的に値の変更を許す宣言のこと。
mutable のついたメンバは const なメソッド内で変更されてもコンパイラは許可してくれる。
使用する側からしてみれば
Client から見て, "変更してもわからない値" を変更するために使う
object の振る舞いがこの変数に依存しない場合に有効
class を MultiThread Safe にするために利用( mutex を mutable にする )
class Camera {
public:
// Matrix 値を参照するだけのため, const
const float *getMatrix() const{
if ( isDirty ) {
data = recalc();
isDirty = false;
}
return data;
}
private:
// 再計算フラグは const メソッド内でも変更を許可してもらう。
mutable bool isDirty; // camera の振る舞いに関係なし
};
■ メソッドにconstをつける
メソッドに const をつけるのは、そのメソッドが
オブジェクトを変更しないという約束をすることになる。
class Stack {
// オブジェクトの状態をかえるので const ではない。
int pop();
// 要素数を教えるだけなので const
int nrElement() const;
};
const なポインタ、リファレンスは、変更できないことを約束しているオブジェクトなので
inspector のメソッドのみが使える。
void test( const Stack &stack ) {
stack.nrElement();
// コンパイラに約束が違うと怒られる。
stack.pop();
};
■ C++ 引数の省略化
C++ は (int foo, bool flag=0L ) とすることで引数を省略できる
POINT
C++ はC の仕様を内包する。( malloc など )
しかしC のクラスを含む構造体をmalloc した際の挙動は不安定
■ Default引数の禁止
コンパイルエラーにかけるため、基本はデフォルト引数は利用しない。
void func( void *a, void *b = NULL, void *c = NULL );
// 変更した
void func( void *b, void *c = NULL );
// a のつもりでいるけど CMPERR にかからない
void f( &obj );
参照に対して DefaultArg はできない ?
GlobalWork を利用すればいい
// 次はできない
void func( bCls &a = 0 );
■ extern 'C' Linkage
目的
C++ から C のコードを呼び出すときに使う。
c のコードがある
c++ 側のコードからよびだす。
リンクする場合に次のエラーが表示される
main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl cfunc(void)" (?cfuc@@YAXXZ) が関数 _main で参照されました。
main.exe : fatal error LNK1120: 外部参照 1 が未解決です。
POINT
コンパイラが生成する外部リンケージ名は, 実際のコード内の関数名とは異なる。
これは C++ のオーバーロードや呼び出し規約を名前に含ませるため。
void cfunc();
// C++ コンパイラが生成する名前は
?cfunc@@YAXXZ
C 言語の呼び出し規約では, 関数名の頭に _ がついた名前になる。
そのため C++ 側でかいた cfunc() は リンク時に ?cfunc@@YAXXZ を探しにいくが
C 側では _cfunc という名前で公開されているためリンクエラーとなる。
そこで C++ 側で関数を呼ぶ時に _cfunc() という名前で呼ぶようにコンパイラに指示する。
通常は C で書いているモジュールを提供する側がこの処理をいれる。
C++ コンパイラでは __cplusplus プリプロセッサシンボルが自動で定義される。
これを利用してヘッダに以下の記述をすると, C++ 側では extern "C" cfunc() として公開される。
// mod_c.h
#ifdef __cplusplus
extern "C" {
#endif
void cfunc();
#ifdef __cplusplus
}
#endif
#include "mod_c.h"
{
// _cfunc() という外部リンケージ名でコールするようにコンパイラがコードを生成する。
cfunc();
}
アセンブラを出力すると外部リンケージ名が見れる。
// cl /c /FA main.cpp
call _cfunc
call ?cfunc@@YAXXZ ; cfunc
Cpp での関数名の名前の変換を許すか、許さないかをコンパイラに指示
C 言語で記述された Program と Link するよ と C++ コンパイラに教える。
cpp compiler に対して関数の改名を抑止
// 関数の先頭で宣言する
extern "C" void foo( void );
1. VarGbl, VarStatic は常駐する.
-> plugin load 後は常に値をもって常駐している点に注意
理由:
C で書かれた関数をC++ から呼び出すことが困難になるから。
既にあるC のライブラリを勝手に変形されては、呼び出しようがない
printf -> xxx_printf() となってしまう。
extern "C" void Cfunc(int);
extern "C" {
void Cfunc1(int);
void Cfunc2(int);
void Cfunc3(int);
}
■ member 関数ポインタの扱い
WARNING
ref
VS.80 .aspx
■ Constructor(コンストラクタ)
コンストラクタはオブジェクトの生成されるときに必ず呼ばれる関数。
オブジェクトの初期化をするために使う。
必ずコンストラクタが実行されるため
オブジェクトは一貫した形式で初期化できる。
class Test {
};
// インスタンス t が生成されるときに t のコンストラクタが自動で実行される。
Test t;
■ コンストラクトする順番
POINT
建物と同じように考えるとわかりやすい。
基礎ができて -> 飾り が追加される
破棄の際は逆
■ 初期化子リスト
クラスのメンバオブジェクトを初期化するには初期化子リストを使う。
コンストラクタ本体の実行の前に、しておくと効率がよい。
class Test {
string m_str;
// メンバオブジェクト m_str を初期化する。
Test::Test() : m_str( "" )
{
// コンストラクタ本体では空ということも多い。
}
WARNING
Test::Test()
{
// 代入では効率がよくない。
m_str = "" ;
}
};
コンストラクタはすべてのメンバと、基底クラスを初期化する。
初期化子リストを明示しない場合でも、コンパイラが自動で初期化する。
■ コンストメンバの初期化
const メンバは初期化子リストのタイミングのみ初期化できる。
class Test {
const string m_str;
// メンバオブジェクト m_str を初期化する。
Test::Test() : m_str( "init" )
{
// 初期化の代入は const に反するのでエラーとなる。
m_str = "test" ;
}
リファレンスのメンバも初期化リストで必ず初期化する。
class Test {
const string &m_str;
// メンバオブジェクト m_str を初期化する。
Test::Test( const string &s ) : m_str( s )
{
// 初期化の代入は const に反するのでエラーとなる。
m_str = s;
}
■ 初期化子リストの順番
クラスのメンバ変数は、クラスの宣言順に初期化が実行される。
基底のクラスの初期化 ---> メンバオブジェクトをクラスの宣言順に初期化
class Test : public Base{
int i0;
int i1;
int i2;
};
// 並べた順ではなく 0, 1, 2 の順番で初期化される。
Test::Test() : i2(2), i1(1), i(0) {}
実際に初期化される順番でかくと間違いをふせげる
Test::Test() : Base(), i0(0), i1(1), i2(2) {}
WARNING
メンバの初期化で別のメンバの値を利用するときは、順番に注意すること。
クラスのメンバの順番を変えただけで、エラーをおこす。
// コンストラクタ本体で代入すれば、順番の依存性を避けることができる。
■ DefaultConstructor(デフォルトコンストラクタ)
引数をもたないコンストラクタをデフォルトコンストラクタという。
クラスの constructor は object 生成時に必ずコールされる。
コードで定義しない場合は, コンパイラが自動生成する。
class T {
int a;
public:
T(){}
};
main(){
// ローカルのオブジェクト a が生成され、DefaultConstructor( 引数なしのコンストラクタ )コールされる
T t;
}
■ 配列のコンストラクタ
POINT
配列の場合は、各要素はデフォルトコンストラクタが起動する。
Test a[10];
Test *t = new Test[10];
そのためデフォルトコンストラクタがないクラスはコンパイル時にエラーとなる。
class Test {
public:
Test( int i );
};
初期化したいときは init() などのメソッドをつかって初期化をする。
Test *t = new Test[10];
for( int i=0; i< 10; i++ ){
t[i].init();
}
または std::vector を利用すると各要素のコンストラクタを指定できるので便利。
vector< Test > a( 10 );
vector< Test > a( 10, Test(2) );
REFERENCE vector
constructor は object [ 生成時 ]に[ 必ず ]コールされる
返り値をかえせない. ( ObjectAdr を返す必要があるから )
-> Object 自身の型を返す
Foo &Foo();
-> ので次のような記述は可能.
Foo func()
{
// Retval を返す際に, CopyConstructor が走る. ie. Foo( const Foo &);
return Foo();
}
POINT
ctor も関数のひとつ. ( Access 制限をうける )
private() にすると自分以外がつくれなくなる
bCls::ctor を call するのは dCls::ctor
-> ので, protected: bCls::ctor && dCls::ctor ならば次は可能.
b *b = new dCls; // ただし B 単体でつくれない.
Class の Component は INIT 時に DefaultCostructor が call される.
-> Cmp::Cmp( int ){} ど定義してしまえば, 明示的に呼ぶ必要あり.
class App {
public:
App(){
// 暗黙に c が生成され, Cmp::Cmp() が call
#if EXPLICIT
c(); // CMP が自動生成. ( なければ CMP ERROR )
#endif
}
private:
Cmp c; // Pointer ではなく, object 自身をもつのもあり. ( heap に作成される ? )
// 内包している分サイズが増大する.
};
class foo {
int a;
public:
foo(){ ... }
}
■ CopyConstructor(コピーコンストラクタ)
DESC
引数に自分と同一クラスをもつ コンストラクタのこと。
値渡し ( PassByValue ) で呼ばれる。
同じ型の 別の Object で "初期化" するときに使う
Foo func() {
Foo *f = new Foo();
return *f; // ここでコールされる
}
POINT
代入演算子と間違えないこと。
インスタンスが生成されるタイミングでコピーコンストラクタは呼ばれる。
Foo a, b;
// CopyConstructor
{
Foo f( a );
Foo f = a;
}
// これはコピー代入演算子
f = b
// 値渡し でもコールされる。パラメータ側のオブジェクトが生成されるから。
// Foo func( Foo );
Foo d = func( f );
■ コピーコンストラクタの自動生成
コンパイラが自動生成する場合は すべてのメンバのコピーコンストラクタを実行する
組みこみ型は bit 単位のコピーがされる。
■ コンストラクタ内で別のコンストラクタを呼ぶ
コンストラクタ内で別のコンストラクタをよぶと別オブジェクトが生成される。
コードを共有したい場合は init() などのメソッドを用意する。
class Test {
public:
Test();
Test();
private:
void init();
};
■ コピーを禁止する
クラスのコピー操作を禁止するには private のアクセスにすることでコピーを利用できないようにする。
File クラスなどはコピーが不要なので、禁止してしまう。
class File {
File( string &path );
private:
// 利用しないので定義はしない
void File( const File & );
}
File f;
// ここでコンパイラがエラーを教えてくれる。
File b = f;
■ ShallowCopy.DeepCopy
ShallowCopy
ポインタだけをコピーすること
DeepCopy
ポインタの指す参照先も含めてコピーすること。
■ コンストラクト方法を制御する
コンストラクタを private 権限にすることで
自分以外がオブジェクトを生成できなくすることができる。
この場合は create() をするスタティックなメソッドを代わりに用意してあげる。
ファイルクラスはロードするクラスのみが管理するようにする。
REFERENCE friend
class File {
// ローダにだけ自分の private を公開する
friend Loader;
private:
// 封印する
File( const string &path );
~File();
};
class Loader() {
public:
File *createFile( const string &path ) {
return new File( path );
}
void destoryFile( File * );
};
{
// Loader の管理しない File を作成することができない
File f;
}
■ 参照型(Reference)
DESC
存在する Object の別名.
実体は Pointer かも ( 実装依存 )
Ref を利用する際は, 存在する Object は何かを意識しよう.
Stack 上の Object の Reference をかえすのは問題あり.
DESC
1. 変数、定数, object に別名をつける。
1. 関数の引数、返り値に使用。
EX :
int n = 10;
int& r = n; // n に別名r をつけたことを意味。メモリ領域を共有している
r = 7; // n=10;
POINT
1. 意味論としては, すでに生成済みの object を渡す( 返す )際に利用する
( ptr とか意識しないこと )
WARNING
1. 参照型の仮引数の場合は cast の際も参照型として cast すること
ERROR
getBV( (v3f)max, (v3f)min );
OK
getBV( (v3f &)max, (v3f &)min );
■ PREFERENCE(C++StdLibrary)
■ std::max min
DEP
#include< algorithm >
template < class T>
inline const T& max(const T& a, const T& b)
{
return a < b ? b : a;
}
// overload 型.
template < class T, class Compare>
inline const T& max(const T& a, const T& b, Compare comp)
{
return comp(a,b) ? b : a;
}
ObjFunc は 関数( PtrFunc )のこと
■ IO(入出力)
■ ifstream
DESC
ファイルを読み込むクラス ( InputFileStream )
fopen と同じように使うことができる。
#include< fstream>
using namespace std;
// ファイルをバイナリモードで開く
ifstream in( "test.bmp" , ifstream::binary );
// ファイルオープンのエラーチェックをする
if ( !in ) {
printf( "cannot open" );
exit(0);
}
// 末尾へ移動 ( 後ろから 0 byte )
in.seekg( 0, ifstream::end );
// 後ろから 4 byte へ移動
in.seekg( -4, ifstream::end );
// サイズを取得する
int sz = in.tellg();
char *buf = new char[sz];
// 先頭にファイルポインタを戻す
in.seekg( 0, ifstream::beg );
// 指定したサイズを buf へ読み込む
in.read( buf, sz );
// 出力
ofstream out( "dst.txt" , ofstream::binary );
小さいサイズのテキストならば operator< < で簡単によめる。
ifstream in( "test.txt" );
string str;
if >> str;
cout < < str < < endl;
■ ofstream
出力するには OutputFileStream クラスを使う
ofstream out( "d:/dst.txt" , ofstream::binary );
int i;
out.write( i );
// 書き込み位置 ( put )を調べる
int pos = out.tellp();
// 文字列をかきこむ
out.write( str, sizeof(str) );
■ IOStream.Lib
DESC
標準入出力の処理をするクラス
stdio.h に相当する処理をしてくれる。
POINT
cout std::ostream クラスのインスタンス
cin std::istream の一例.
cerr std::istream の一例.
istream は STDIN から入力をうけつける. ( kb ではない. )
ostream は STDOUT へ出力する( Console ではない )
POINT
printf() と違ってタイプセーフ。
書式指定の間違いも起きない。
#include< iostream>
using namespace std;
// 任意の型に対して, 自動的にオーバーロードされたメソッドが呼ばれる。
int i = 7;
cout < < i < < endl;
string s = "test" ;
cout < < s;
double d = 0.1;
cout < < d < < d;
std::istream
標準入力を担当する
std::ostream
標準出力を担当する
C でいえば stdout に当たる
■ istringstream
文字列をつかってコンストラクをして任意の型に変換する。
atoi, atof の代わりに使う。
#include< sstream>
using namespace std;
istringstream iss( "10" );
int i = 0;
iss >> i;
// エラーの検出をする
iss.error();
■ C++ cast
DESC
cast: 型変換をコンパイラに明示
型変換
int -> double
double dval = (double)ival;
型変更( 中身は不変 ? )
long -> int *
int *iptr = (int *)lval;
const 外し
const int ciptr;
int *iptr = (int *)ciptr;
cpp
-> cast の目的をコンパイラに明示
static_cast: 暗黙の型変換があれば変換
int -> long
lval = static_cast< long>(ival); // OK
void *-> int
ival = static_cast< void *>ival; // ERR
reinterpret_cast : ptr 同士の変換 | ptr < -> int( ptr アドレスは不変 )
POINT
互換性のない ptr 同士のキャストが使いどころ
int * -> long *
lptr = reinterpret_cast< lptr>(iptr);
const_cast : const, volatile 外し
cint * -> int *
iptr = const_cast< int *>ciptr;
cint -> int
ival = const_cast< int>cival; // ERR( ptr 以外はだめ ? )
dynamic_cast : CBase から CDerived への型保障キャスト (逆)?
POINT
upcast : CDerived -> CBase
downcast : CBase -> CDerived
( UML 参考 )
CHuman -> CMan
humanptr = dynamic_cast< CHuman *>manptr;
WARNING
dynamic_cast が使用できるのは, 仮想メンバ関数をもつ場合
■ Destructor(デストラクタ) ■ Alignment(境界配列)
DESC
データ型のサイズにより、2, 4, 8の倍数のアドレスに配置されること
ハードウェア的な制約もあれば
より高速な処理をするためにコンパイラ(リンカ、ローダ)が指示するものもある。
メモリ、CPU 間でやりとりできるデータのサイズは 16bit, 32bit だったりする
整列しておけば 4byte のデータを 1回で転送できる
Alignment をする理由
POINT
変数は宣言順に確保されるとは限らない
ローカル変数は宣言した順に, 配置されない
構造体の各メンバは宣言順だが, Alignment されて、メンバの間には隙間( padding ) が空く場合もある
構造体のサイズが padding されるのは, 各メンバが適切な位置に配置されるため隙間が空いてしまう。
隙間が空くのを防ぐにはメンバの構成を入れ替える。
または padding を明示しておく。
BAD
struct Foo {
int i0;
short s0;
int i1; // int 型は 4 の倍数で配置されるため s0, i1 の間に隙間が空いてしまう。
short s1;
};
// OK
struct Foo {
int i0;
int i1;
short s0;
short s1;
};
// OK
struct Foo {
int i0;
short s0;
char pad[2];
int i1;
short s1;
};
Compiler の指定 で 変更可能( vc: 構造体 mem への alignment )
-> mem , cpu は 32 本の線で結合
-> 可変サイズで, Buffer をもつより, Alignment を守って大きめのサイズにした方が都合がよい場合もある.
// sizeof(S) = 2
// 2 コ で 4 byte
struct S{
short s;
};
// sizeof(C) = 1
// 4 コ で 4 byte
struct C{
char c;
};
// sizeof(S3) = 6
struct S3{
short s0;
short s1;
short s2;
};
x86 系では, double は 8ByteAlignment にすると効率がよい。
Architecture によっては必須になる.
Alignemnt をそろえた, memory の取り方
data = (void *)myMalloc( size + 32 ); // 32 byte Alignemnt
s_data_org = data; // ori 保存
data = (void*)(((u_int)data + 31) & ~31 ); // Alignemnt Macro
POINT
struct cls mem 内が連続しているとは限らない
現実問題として, float, short, char, が struct 内に並ぶ場合は 連続して配置されている
enum も連続していると考えられる
// 12 byte
struct Foo{
char c0; // 1 の倍数で alignment 1
float f0; // 4 の倍数で alignment
char c1; // 1 の倍数で alignment
};
// 残り3byteを埋めないと次の構造体を配置した場合にf0の配置に問題がおきる。
[c0][ ][ ][ ][f0][f0][f0][f0][c1][ ][ ][ ]
// sizeof(Foo) = 8 FILE: main.cpp
struct Foo{
char c1;
char c2;
float f;
};
■ プラグマでアラインメントを指定する
SYNTAX
__declspec( align( # ) ) declarator
コンパイラのプラグマでアラインメントを指定する。
VisualStudio では以下の方法で指定する。
// Vector 構造体を 8 byte の境界の配置するように指示する
__declspec( align(8) ) struct Vector
{
short x;
short y;
short z;
};
typedef struct ALIGN(8) { short vx, vy, vz, pad; } SHORT_VECTOR;
アラインメントを指定すると, アドレスが 8 byte の境界にあることがわかる。
Vector v;
int adr = (int)&v;
adr %= 8;
printf( "addr %d %d" , &v, adr );
POINT
alignment の指定は 2 冪でないとだめ
ERROR C2344: align(12) : アライメントは 2 の累乗でなければなりません。
__declspec( align(12) ) struct SA
■ キャッシュライン(CacheLine)
よく使うデータはキャッシュラインに揃えることで、キャッシュミスが置きにくくなりプログラムの速度があがる。
cacheは、16bytesごとの塊xN個で構成されている。
この16bytesの塊1つを、1lineという。
lineが何個あるかは、cacheサイズをlineサイズで割れば計算できる。
1KB / 16 = 64なので、このcacheには64line存在する
POINT
良く使うデータや、連続して使うデータが同じcache lineに割り当てられてしまうと、cache missが頻発してしまいます。
■ アラインメントを指定しない場合
Object はまず固有のサイズに対するアラインメントをもつ。
int は 4byte 境界に、 double は 8byte 境界に配置される。
int は 4 で割り切れる場所にないといけない。
クラス内にあるメンバオブジェクトは、オブジェクトのアラインメントのうち最小の値の位置にアラインメントされる。
struct Test {
char c;
short s; // 2byte 境界にアラインメントするため, c と s 間に 1byte のパディングがはいる
};
2. (push 8) は 1 の alignment の設定と比較してより小さい alignment が優先される
3. -> 適当に詰め物をする
// 4 Byte 境界に押し込め と命令
#pragma pack(push, 4) -> 12 byte
struct Foo
{
char B; // 1 byte alignment
double W; // 4 byte alignment( 8 より優先 )
char H; // 1 byte alignment
short s; // 2 byte alignment
};
#pragma pack(pop)
POINT
プリミティブな値は、min( szObj, Alignment )の倍数のアドレスに配置する
一回で CPU に data を渡せるように箱に詰め物をするのが Alignment
構造体は 2冪サイズにするべき ( 最低でもキャッシュライン倍数にする )
■ 文字列(String)
DESC
c++ 標準ライブラリとして用意されている。
C-String と比較して次のメリットがある
安全性: 配列境界を超えない
利便性: 標準C++ 演算子の使用が可能
一貫性: 文字列という型を定義できる
sizeof( string ) = 4;
文字列を格納する際に, new, delete をしている
string 型は クラステンプレート basic_string の実装のひとつ
typedef basic_string< char, char_traits< char>, allocator< char> > string;
// 空 string
string();
// C_String str を使用して生成
string( const char *str );
// 他のstringを使用して string 生成
string(const string &str);
演算子
+
string + string
string + c_str
c_str + string
c_str + string
// Compile ERROR にならないが "7" は追加されない
string s;
s += 7;
■ 結合
string s;
s += "a" ;
s += "b" ;
s += "c" ;
// "abc"
printf( "%s" , s.c_str() );
s[1] = '\0';
// "a"
printf( "%s" , s.c_str() );
■ 文字数
SYNTAX
size_type size();
// 3
string s = "abc" ;
s.size();
■ 参照
// 参照
char operator[]( idx );
■ 検索
SYNTAX
size_type find(
const string &str, size // 検索する文字列
_type pos = 0 // 検索を開始するオフセット
);
RET
N : 見つかったインデックス位置
string::npos : 見つからない
// 検索
if ( s.find( "abc" ) != string::npos ) {
}
// 後ろから検索
s.rfind( "abc" );
if ( string1 == string2 ){}
string1 = string2; // string を代入
string1 = "foobar\n" ; // C_string を代入
パスからファイル名をかえす。
string filename( const string &path )
{
size_t i = path.find_last_of("/" );
string ret = path.substr(i+1, path.length());
return ret;
}
string dirname( const string &path )
{
size_t i = path.find_last_of("/" );
string ret = path.substr(0, i);
return ret;
}
■ 部分文字列
substr
SYNTAX
basic_string substr(size_type pos = 0, size_type n = nr) const;
DESC
pos で指定した index から n 個の文字列を返す。
assign ( 部分列. )
str から、pos文字目から、n文字を取り出して設定
assign( const basic_string& str, size_type pos = 0, size_type n = npos );
■ 比較
compare ( 比較 )
// 文字列定数sのn文字までとの比較を行う。
int compare(const char* s, size_type pos, size_type n) const;
string s = "foo bar g" ;
// 3
int nr = s.find(" " );
// 7
int nr = s.rfind(" " );
// 0 番目から 3 個
// foo
s.substr( 0, 3 );
// 見つからない場合は, -1
int nr = s.rfind("foo" );
POINT
string class はコンテナ
∴ 文字列の先頭、末尾を指す、begin(), end(), size() をサポート
static にした場合は, Compiler 単位で作成されるので重複してしまう.
// foo.h 重複しない.
extern const char *strtbl[];
// foo.cpp
const char *strtbl[] = {
"a" ,
"b" ,
"c" ,
};
// foo.h -> 重複する.
static const char *strtbl[] = {
"a" ,
"b" ,
"c" ,
};
■ 参照(Reference)
Reference とはオブジェクトの別名のこと。
関数の引き数の参照渡しでよく使う。
// 参照 渡し
// const obj は変更不可
//float func( float &foo )
{
float foo = 0.0f;
// bar は float 型としてうけとる( うけとることが可能 )
// bar, foo は別モノ
#if 0
float bar = func( foo );
#else
// foo の別名となる
float &bar = func( foo );
#endif
bar ++;
printf( "foo[ %f ]\n" , foo );
return 0;
}
引数渡し、返り値、共に普通に渡せばOK
{
int &foo( int &b ){
static int a;
return a;
}
}
int c = 77;
int &d = foo( c ); // 普通に渡す && 普通に受け取る
d = 12; // a = 12 になる
int d = foo( c ); // 参照としてうけとらない場合は、int 型を返しことと同一
d = 12 // a = 0 のまま
POINT
private 変数 を参照返しすることで、get, set 相当のことをする
private:
int x;
public:
int &getX()( return x; )
WARNING
ローカル変数の参照を返してはいけない ( ∵ 参照先は消滅 )
STL vector< int> arr[i] はローカル変数なので、参照として返してはいけない
std::vector< int> arr
return arr[i]; // ERROR;
参照変数 は定義する際に、初期化が必要( ∵ 変数の別名, compiler の立場で考えれば自明 )
ERROR
int &foo;
int a;
foo = a;
OK
int a;
int &foo = a;
// 参照 の考察
{
int b = 7;
int c = 10;
int &a = b; // b は a ともいう
a = c; // a に代入 == b に代入
cout < < b; // RET 10
}
// cls の member で reference がある場合は, 初期化list でおこなうこと
■ リファレンスの初期化
リファレンスを作成する場合はリファレンス先への参照が必須になる。
ref の初期化時点 ( 逆に初期化が必須 )
// BAD
int &k;
// OK
int &j = i;
■ リファレンスとポインタの違い
POINT
ポインタを定義するとポインタ型の別オブジェクトができる。
リファレンスは既存のオブジェクトの別名なので新規のオブジェクトはできない。
■ リファレンスを返す
リファレンスを返すことで、関数コールは左辺値となる。
class Array
{
public:
float &operator[] ( unsigned int i ) throw( outOfRange );
private
float data[100];
};
arr::operator[]( unsigned int i )throw( outOfRange )
{
if ( i>=100 ){
throw outOfRange("array idx is out of range" );
}
return data[i];
}
{
Array a;
// 左辺値に値 7 を代入する
a[10]= 7; // obj::data を変えることを意味
}
■ 仮想関数
■ 仮想関数でフレームワークをつくる
SAMPLE
シンプルウィンドウ サンプル
フレームワークをつくる場合は先に制御フローをつくってしまい
アプリケーション固有の処理を仮想関数をつかってハンドラとして用意してしまう。
OpenGL のフレームワークをつくる。
不変な部分をシステム( フレームワーク )側で用意する。
そして可変な部分を 仮想関数 にしてアプリケーションごとにカスタマイズさせてあげる。
class GLFramework {
public:
// アプリケーション固有の処理の仮想関数として用意しておく。
virtual void onRender();
// 固定の内容は仮想関数にしない
void run();
};
アプリケーションは Framework の流れに沿って必要な部分( カスタマイズ )したい部分を
上書き( オーバーライド )する。
class Sample : public GLFramework {
public:
// 赤くかく
void onRender() {
glClearColor( 1, 0, 0, 1 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
};
class Sample2 : public GLFramework {
public:
// 青くかく
void onRender() {
glClearColor( 0, 0, 1, 1 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
};
後は実行するだけ。全体の流れはフレームワークにまかせる。
#include "glframework.h"
int main() {
{
Sample1 app;
app.run();
}
{
Sample2 app;
app.run();
}
return 0;
}
フレームワーク側は共通に必要となる処理を用意しておく。
ウィンドウをつくったり, メッセージループを回す部分は共通の処理なのでしておく。
不変な部分にあわせてシステムをつくる。
GLFramework::GLFramework {
// ウィンドウをつくる
m_hWnd = CreateWindow();
}
メッセージループを回す
void GLFramework::run() {
while ( PeekMessage() ) {
// 仮想関数をよんであげる。これでアプリケーション固有の処理がよばれる。
onRender();
}
}
■ virtual 関数の使いどころ
具体的な処理を派生クラスにまかせるときに使用する。
基本クラスで宣言され、派生クラスで再定義されるメンバ関数
仮想関数の再定義において、prototype が同一のものをoverride : 異なるものを overload
POINT
共通部分を探ることが重要
その部分を virtual 関数にする。
拡張が想定される位置を仮想関数にする。
仮想関数にすることで、関数テーブル経由でメソッドを動的にコールすることができるため
可変になるということ。
カスタマイズ可能という意味。
基底クラス側でハンドラとして用意しておき、実際に利用する側( 派生クラス側 )が具体的な処理をかく。
class Converter {
void output( const string &path );
}
POINT
関数ポインタでも同様のことができる
Application::display(){
virtual void Task::main(); // 具体的な処理は派生先にまかせる。
}
広範囲の集合に対して処理をする際に有効
DESC
virtual は呼ばないと カスタマイズされているかわからない
virtual とは [ 同様にみなせる ] オブジェクトであることを保障する方法
型の継承の意味
POINT
virtual void save();
-> カスタマイズ可能 save() メソッド
// virtual は呼び出す側の再利用を意味する
for( i=0; i< 10; i++ ){
item->save(); // 固有のサービスがコールされる
}
■ InterfaceClass
DESC
interface class を使用することの目的は
共通の規格( ふるまい )を強制すること。
フレームワーク( 呼ぶ )側の再利用が促進される。
■ 抽象クラス
DESC
純粋仮想関数 をもつクラス
POINT
[ 純粋仮想関数だけ ]が定義されているクラスはインターフェースという
純粋仮想関数 を含む class を抽象クラス ( abstract class )と呼ぶ
abstract class は生成できない。しかし pointer は OK
POINT
継承されることを前提としたクラス( デザインパターン的視点 )
純粋仮想関数を含むクラス( 文法的視点 )
POINT
クラスが派生されてからしか決定できないようなデータを使うときに抽象クラスを使用
POINT
抽象基底クラスの意味( 設計的 )
DESC
純粋仮想関数により、フレームワークの提供側としては
default の振る舞い
仮想関数の[ 強制的な使用 ]を示すことになる
Task->Run();
Window->Display();
■ vtable( 仮想関数 table )
DESC
cpp コンパイラが自動で method 第一引数に this ptr をいれていくれる
POINT
vtable とは FunctionPointer Table のこと
Class ごとの情報のひとつ
Debuger でみると __classType という名前がみれる
オブジェクトのディスパッチテーブルはオブジェクトの
動的にバインドされるメソッドのアドレスを保持する。
コンパイラが virtual 宣言を見つけると、テーブルを作成する。
Method の Call は
アドレスをオブジェクトのディスパッチテーブルからとって利用する
ディスパッチテーブルは同じクラスに属するオブジェクトでは全て同じ
通常オブジェクトから共有される。
互換性のある型のオブジェクトは同じレイアウトのディスパッチテーブルを持ち
メソッドのアドレスは
全ての型互換のクラスの中で常に同じオフセットに現れる。
メソッドのアドレスをディスパッチテーブルから取り出すことで
オブジェクトの実際のクラスに対応したメソッドが得られる。
各クラスのインスタンスは関数テーブルへのポインタをもつことになる。
そのため仮想関数をもたないクラスよりポインタのサイズ分が増える。
class Job () {
int i;
public:
virtual void run(){}
};
// 8
Job j;
printf( "size %d" sizeof( j ) );
1. static const char *item[] = {
"run" ,
"title" ,
"menu" ,
"reset" ,
"exit" ,
};
for ( int i=0 ; i < sizeof(item)/sizeof(item[0]) ; i++ ) {
2. // 次の2項目を相手に渡す
// 渡し方としては
// 1. ReqAdd の ptr を渡す。
// 2. メンバ関数で id, str を渡す. こちらの方がスマート
//
// スマートでない方法は data[] = { data0, data1, data2 ... } サイズの計算がメンドイ
// tgt->sendSignal( sig, data );
//
// 送るべき内容を message Class としてまとめてしまう。
//
// 各 message はそのサービスをするクラスが内包すれば OK
//
1. code での マクロの反映方法( rebuild は必要 )
#if defined(_WIN32)
vsprintf(d.top, form, arg);
#else
vsnprintf(d.top, LINE_SIZE, form, arg);
#endif
■ 関数のオーバーライド
DESC
sub cls で base cls の関数を overwrite しないならば、base cls のものが使用される
sub cls で宣言した 仮想関数は, 実装しないとコンパイラに怒られる。
POINT
ABC の実装はすべての派生先で必要か ?
一度でも 実装すれば、後の派生先は実装しなくても OK
virtual static を併用可能か ?
DESC
できない( ERR: virtual メンバ関数は静的メンバ関数にはなれません )
■ オーバーロード
関数のオーバーロードとは同一スコープで同じ名前をもつが signature が異なる関数こと。
class Test {
void func( int );
void func( float );
};
POINT
signature とは次の構成のこと
関数名 ( パラメータ型と順序 ) const, volatile の有無
以下は異なる signature
void func( int ) const ;
void func( int );
オーバーライドは異なるスコープで同一の signature をもつこと。
派生クラスで仮想関数テーブルを上書きしてしまうこと。
class Base {
virtual void func();
};
class Derive : public Base {
void func();
};
■ クラス継承間での同名の関数
派生クラスと基底クラスで同名の関数があると、内部スコープの名前が外部スコープの名前をかくす。
そのため、このような状態にならないような名前をつけることが大切。
class Base {
void func( int );
};
class Derive : public Base {
// 同じ名前
void func( float );
};
Derive d;
// Derive::func( float )が呼ばれる
// 派生先の名前 ( func )が基底クラスの名前を隠してしまうということ
d.func( 10 )
POINT
1個のクラスはひとつのスコープになる。
■ 仮想関数のコンストラクタの呼び出し
コンストラクタ内で仮想関数を呼ぶとそのクラスのメソッドが呼ばれる。
もし派生先のメソッドが呼ばれると 派生のメンバを操作することになるためこの動作は正しい。
class Derive : public Base {
void func();
};
Base::Base {
// これは Base::func() が呼ばれる。
func();
}
■ 仮想コンストラクタ
WARNING
コンストラクタは仮想関数にできない。
理由は 基底クラスのコンストラクタがコールされている時点では
派生クラスのメンバが初期化されていないから。
■ スコープ演算子をつかう
仮想関数内で、スコープ演算子をつかうことでクラスのメソッドを明示できる。
仮想関数テーブルを経由せずに( パイパスして )明示的に呼ぶときに使う。
class Gui {
virtual void move() {}
};
void Button::move() {
// 基底クラスの move() を再利用する
Gui::move();
// Button 固有の move() 処理をかく
}
クラスの利用者はこの機能を通常はつかわない。
クラスの開発者が、派生クラスのメソッド、コンストラクタ、デストラクタ内で便宜上つかう。
■ 純粋仮想関数(PureVirtualFunction)
WARNING
抽象基底クラスは デストラクタの "定義" をもつ必要がある。
リンカーが派生クラスのデストラクタから呼ばれる Base::~Base() を探すため。
class Base {
public:
virtual ~Base() = 0;
};
class Derive : public Base {
};
int main() {
Derive d;
return 0;
}
定義すればリンクエラーにならない。
派生クラスをインスタンス化するには、純粋仮想関数のすべてを実装する必要がある。
このときに、継承元をたどって実装されていればいい。
要は仮想関数テーブルが埋まっていればいいということ。
class Base {
public:
virtual void f() = 0;
virtual void g() = 0;
};
class Derive1 : public Base {
void f();
};
class Derive2 : public Derive1 {
void g();
};
■ 非インライン関数
仮想関数をもつクラスはひとつの 非インラインメソッドをもつべき。
class Base {
public:
virtual ~Base();
};
class Derive : public Base {
};
int main() {
return 0;
}
■ cast(キャスト)
型変換
// int -> double
double dval = (double)ival;
//
long -> int *
int *iptr = (int *)lval;
reinterpret_cast : ptr 同士の変換 | ptr < -> int( ptr アドレスは不変 )
■ reinterpret_cast
互換性のないポインタ同士のキャストに利用する。
reinterpret_cast は偽者の型でもキャストできてしまう。
安全性という視点では使うのは良くない。
// int * -> long *
lptr = reinterpret_cast< lptr>( iptr );
dynamic_cast : CBase から CDerived への型保障キャスト (逆)?
POINT
upcast : CDerived -> CBase
downcast : CBase -> CDerived
POINT
cpp スタイルのキャストを利用することで, キャストの意図を明確にする
WARNING
キャストは 飽くまで [ 型がこうである ] と コンパイラに言い聞かせるだけ。
( 保障は一切ない )
だから Cast は最低限にするべき ( C++ ならば Template で代用すること )
void onClick( Gui *gui ) {
Button *b = reinterpret_cast< Button >( gui );
}
POINT
アセンブラでは型という概念がない
アドレスを指定して, Data を Add, するだけ
中身( アドレスの指す先の bit 列 が Integer )を意味するかどうかは プログラマが責任ををもってしていた
しかしそれば手間で、中身を間違うという事故があったため、型というルールができた。
// キャストの内容によってメモリ上の値が変わることも、不変な場合もある
// VisualStudio のメモリウィンドウで確認できる。
int i = 128; // MEM IMG [ 0x 80 00 00 00 ]
int i2 = i; // MEM IMG [ 0x 80 00 00 00 ]
float f = i; // MEM IMG [ 0x 00 00 00 43 ] // ここが43となる点に注意
結論
アドレス( という数値 )を格納する場合は, 変更されない.
なぜなら adr の指す先の Object をこう読んでね とい言うようなもの.
数値型の場合は, 型によって表現方法が異なるので, bit 列が変更される.
base obj を derive obj の ptr に cast して操作すれば,問題があることを意識する
■ const_cast
const, volatile 外し
// cint * -> int *
iptr = const_cast< int *>ciptr;
// cint -> int
ival = const_cast< int>cival; // ERR( ptr 以外はだめ ? )
■ static_cast
DESC
[ 暗黙の型変換 ]がある場合のみ変換できる。
[ 安全な型変換 ]
暗黙の型変換があれば変換
安全な cast を意味するので, c_style_cast より積極的に利用するべき
OK
// void * < -> any *
-> down cast の場合, 派生先の領域にアクセスするのは危険かも
// upcast
Bcls *p = static_cast< Bcls *>( pDcls );
// const をつける
int p = 10;
const int cp = static_cast< const int >( p );
cp = 20; // error: assignment of read-only variable `cp'
static_cast:
int -> long
lval = static_cast< long>(ival); // OK
void *-> int
ival = static_cast< void *>ival; // ERROR
ERROR
Foo *p = static_cast< Foo *>( pBar ); // 継承関係のない cls
3. dynamic_cast
ptr が指す obj の型を取得( obj がどの mem func を support しているか確認という意味 )
WARNING
polymorphic cls のみ( ∵ vtbl 必要 )
4. reinterpret_cast
指定された型に[ 読み替える ]のみ
-> 無理やり別の型に変換する.
void *p = reinterpret_cast< void *>( &fooObj ); // ptr ads は不変
WARNING
多重継承していると 問題が起きる
POINT
// コンパイルエラーで出力される内容が正しい型変換を意味する
MSG:: c style cast || static_cast が必要です
■ dynamic_cast
POINT
オブジェクトがあるインターフェイスをサポートするか調べるときに使う。
派生クラスから基底クラスへダウンキャストする時に使う。
[ ptr がさす obj の型を取得 ] として考えれば OK
dynamic_cast が使用できるのは, 仮想メンバ関数をもつ場合
Base *p = new Derive();
Derive *d = dynamic_cast< Derive *>(p);
Another *p = new Another();
// キャストできない場合は NULL を返す
Derive *d = dynamic_cast< Derive *>(p);
DESC
base -> derive ptr への cast
constructor 時に dynamic_cast< derive *>(this) としても 変換は不可能
construct 時に 処理をわけることは不可能
WARNING
class が virtual 関数を含む必要あり == polymorphic class の必要あり( compile err )
■ 演算子のオーバーロード
関数の overload と同様に 演算子 を overload( 拡張 )する仕組み
C++ では 同一の関数名でも, 引数が違えば 定義できる
ただし 返値 のみが異なる場合は無理かも
( gcc では可能 )
POINT
使いどころは、よく使う関数を短い記述で書くときに使う。
表記は演算子だが、関数がコールされる。( 表現の問題 )
vec3 x, y, z;
z = x.add( y );
// 演算子をオーバーロードすると簡単かつ直感的にかける
z = x + y;
vec3::operator +( const vec3 &rhs )
vec3::add( const vec3 &rhs )
WARNING :
operator->() は禁止.
class T{
public:
T* operator->(){ return this; }
int Fuga(){ return 4; }
};
// 中略
void main()
{
Hoge hoge; // この定義と矛盾して見える.
printf("%d\n" , hoge->Fuga() ); // 4 hoge の定義と離れていたら悪夢
}
WARNING
// operator=() は ちょうど 1 ARG が必要
operator= の signature は 現在 2 つある. ( 組み込み型と同様にふるまうようにするには, 自身の参照をかえす. )
Cls &operator=( const Cls &);
次は operator=() が call されない. なぜかわかる. ?
{
Foo f1;
// CopyCtor が走るらしい. ( CMP /Od にしても発生. -> 要は CopyCtor と同じ意味. ? )
Foo f2 = f1; // Call されない.
// OK
Foo f2;
f2 = f1;
}
POINT
Foo f = 10; -> f(10); に変換される
-> Foo( int ) がないと cmp におこられる.
-> メタファーとしては, char *str[] = {"aa" , "bb" , "cc" };
1 引数のみは Foo = 10; のような記述できる
引数をひとつだけとる ctor のことを,“変換コンストラクタ(converting constructor)”という
POINT
left operand は暗黙として this pointer で渡される
WARNING
演算子が受け取る param list 数の変更は不可
POINT
通常の演算子と同様に動作することを保障すべき
class integer{
int val;
public :
int operator +( integer obj ){
return this->val + obj.val;
}
}
■ operator++
class point{
public :
int x, y;
void operator ++( int n ){ // 後置を表現する prototype
x++; y++;
}
point operator ++(){ // 前置
x++; y++;
return *this;
}
}
POINT
default 後置 increment は値を返却 -> increment
int val = 3;
int foo = val++; // foo = 3;
point o1, o2;
o2 = ++o1;
o2++;
■ operator[]
添え字演算子
class array{
int arr[2];
public
int & operator []( int n ){
return arr[ n ]; // これで参照を返すことになる ?
}
}
■ operator()
SYNTAX
void operator()()
DESC
Functor と言われるクラスがもつメソッド
関数のフリをしたクラスに使う。
class Foo
{
public:
void operator()() {
printf("fucntor\n" );
}
};
{
Foo f;
f();
}
■ operator=.代入演算子
member 変数をすべてコピーする。
コンパイラが自動生成。
■ operator< <
DESC
operator< < (a, b) == a< < b
ostream& operator< < (ostream& o, const fraction &n ) throw()
{
return o < < n.nr < < "/" < < n.denom;
}
■ operator ++
SYNTAX
T& operator++()
DESC
前置インクリメント演算子
この式は先にインクリメントする前の値を返すこと。
class Number {
Number & operator++() {
m_i ++;
return m_i;
}
int m_i;
};
■ operator ++(int)
SYNTAX
T operator++(int)
DESC
後置インクリメント演算子
この式はインクリメントした後の値を返すこと。
class Number {
Number operator++(int) {
// ここでコピーコンストラクタが発生する
Number old = *this;
m_i ++;
return old;
}
int m_i;
};
■ OperatorNew
new 演算子の処理は自前で定義することでカスタマイズできる。
■ perator newをオーバーライドする
メモリを確保する処理をオーバーライドする。
void *operator new( size_t sz ) {
gCnt ++;
return malloc( sz );
}
POINT
ペアになるように operator delete も定義する。
void operator delete( void *p ) {
gCnt --;
free( p );
}
■ 追加の情報をわたす
operator new() は引数に応じたものが呼ばれる。
最初の size_t 引数は必須の項目。その後は任意。
デバッグ情報をうけとる operator new では 2つの引数を追加する。
operator new( size_t sz, const char *file, int line ) {
}
int *a = new( __FILE__, __LINE__ ) int[4];
メンドイのでマクロにする。
#define NEW new( __FILE__, __LINE__ )
int *a = NEW int[4];
■ mallocする領域に追加する
確保するメモリの前後にマークをつけることでメモリ破壊を検地する。
void *operator new( size_t worksz ) {
int sz = worksz + sizeof( int ) * 4;
char *p = (char *)malloc( sz );
int *pt = (int *)p;
pt[0] = worksz;
pt[1] = 12345678;
pt = p + sizeof( int ) * 2 + worksz;
pt[0] = 12345678;
return p + sizeof(int)*2;
}
■ malloc.のように失敗しても NULL を返さない
// これは OK
void *p = malloc( 1024*1024*1024 );
if ( p == 0 ) {
printf("malloc fail\n" );
}
// new は失敗すると Exception をなげる
class Test{
int i;
};
try {
Test *p = new Test[350*1000*1000];
}
// ここで補足しないと
catch(...){
printf("fail new\n" );
}
// NULL を返したいなら new(std::nothrow) を利用する
// 過去の互換性をとるための new
int *p = new( std::nothrow ) int[350*1000*1000];
if ( p == NULL ) {
printf( "new fail" );
}
// WARNING
// Exception を投げないのは Memory 確保のときだけ
// その後の ctor で投げられたら OUT
class Test
{
int i;
public:
Test(){
printf("Test ctor\n" );
throw( bad_alloc() );
}
};
// Memory 確保に失敗すると NULL をかえす
// 成功すると ctor をよぶ -> ここで Exception を投げられる可能性はある
Test *p = new( std::nothrow ) Test[1*1000*1000];
if ( p == NULL ) {
printf("new fail\n" );
}
■ 宣言しなくてもメンバ関数はstatic扱い
DESC
メモリブロックの指定は外からする必要あり
void *operator new( size_t t ) {
// `this' is unavailable for static member functions
// MemBlk を取得する関数に this はない
dpi( this->data );
// この後に constructor が実行される。
return work;
}
POINT
static な GlobalNew は FileScope で利用される
-> ! WARNING がでる. !
LocalDelete() を定義しても, dtor は呼ばれるらしい
[ System ] -> Cls::dtor() -> Cls::operator delete(); という順番らしい.
operator delete() はあくまで, Memory の解放のためにあるようだ.
class Foo
{
public:
~Foo(){ dps("Foo dtor" ); }
// うばう. dtor は走る.
void operator delete( void*p ){
printf("operator delete" );
}
};
WARNING :
WARNING C4211: 非標準の拡張機能が使用されています :
EXtern が static に再定義されました。
POINT
優先順
Global < FileScopeGlobal < ClassLocal の operator new
実は, new, delete は Memory を確保に失敗すると Exception を投げる
EXception を投げる際も, new, delete が呼ばれる
どこから new, delete をどこから利用しているかわからない.
だから Exception を投げずに, NULL を返す仕様の方がよいかも
Constuctor は new XXX の形でしか呼べない. ?
size_t 以外の OperatorNew ( PlacementNew ) を利用した場合は,
Overload になるのかな ?
-> 隠ぺいされてしまう
-> 隠蔽されない. ! Build できてしまう.
WARNING C4345: 動作変更 :
形式 () の初期化子で構築される POD 型のオブジェクトは [ 既定初期化 ] されます
この WARNING の考察.
次の条件の際におきる.
DefaultCtor 未定義. && Foo() // () ありの ctor ( -> 0 初期化. )
&& PlacementNew の定義.
() なしならば, 0 初期化されない.
SOL
ctor で () をつけない. ( 2 種類ある )
要は
PlacementNew ないで, Object を初期化しても
DefaultCtor で 0 Clear しますよ ! と言っている
POINT
PlacementNew をする場合は, ctor をしっかり定義すること
STL は Cpp の CMP に必ず付属する. ( だから PS3 でもある. )
-> STL の Allocator 指定は,
要は, Memory をここからとってよ ! という意味.
自分の中で, new する場合は,
別途の領域から取得する.
実は operator new は Method なので, OverWrite, OverLoad が可能.
-> public に Access されるので, public にする必要がある.
Class 内で宣言したものは,
要は, Object の生成空間を指定する際に使う.
-> さらに継承されるので, 継承によって, Pool を指定することもできる.
// Memory を指定するための クラスを用意してもいい.
// 継承すれば, OK ! ( MultiInheritance )
//
// MemorySelect を利用したもの同士を継承してしまうと競合がおきる.
//
class MemorySelect {
};
派生先で定義した場合は, OverWrite される.
-> しない場合は, bCls で[ 定義したもの.ま.で ]さかのぼる.
operator new( size_t , ... ) 引数リスト自体を カスタムすること可能
-> 逆に呼び出したい new の Signature にあうように指定する必要あり.
ClassLocalNew を定義した場合は, new Foo; では GlobalNew は見に行かない.
-> このあたりは Scope の解決の問題かも.
operator new ( size_t sz, void *memBlk ) {
...
}
new( gWork ) Array();
Class の外で定義したものは, Global( 誰からも見れる )New, GlobalDelete となり,
new , delete 時に call される.
-> operator new も operator の考えに基づけば, 関数のひとつ.
さらに system の new も malloc() を呼ぶらしい. ?
-> ので自分で NewGbl を overwrite する場合も, malloc 利用して OK
C:\Program Files\Microsoft Visual Studio 8\VC\crt\src\new.cpp
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
// malloc() している.
while ( (p = malloc(size)) == 0 )
if (_callnewh(size) == 0)
{ // report no memory
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
■ 初期化リストは必ず記述
WARNING
仮引数は _data とする
foo()::foo(int _data)
: data(_data)
■ Explicit
DESC
変換コンストラクタに利用しないようにコンパイラに知らせる。
変換 Constructor が起動して ClassA( 111 ); となり 一時 ATmp ができる.
operator = () がはしる.
POINT
Copy する必要がある場合は, 明示的に定義する.
不要ならば, 禁止する
class T
{
public:
explicit T(int arg);
};
void main() {
T a( 10 );
T b = 10; // ERR [10] ---> aTmp[A(10)] ---> b = aTmp; を禁止する.
}
■ メンバー関数ポインタ
次のように展開するとわかりやすい
メンバ関数は通常の C スタイルの関数とはシグネーチャが異なる。
暗黙の this ポインタをもつ。
func : Foo::memberFunc() を指す Ptr なので, Foo に func() という FuncMem がなくても OK .
object->*func() === object->(*func)() === object->memberFunc()
C3837 :
FuncMem の アドレスを渡す場合は, &Foo::func; と明示する必要あり.
class Foo
{
public:
void func();
};
POINT
クラスのメンバ関数の型を typedef する。
関数ポインタ型の構文はわかりずらいので typedef で別名をつけると便利。
コンパイラに void T::( void ); 型を FuncPtr であると教える。
#typedef void ( T::*FuncPtr)();
{
// p はメンバ関数を指すためのポインタ変数。
FuncPtr p;
// メンバ関数ポインタにメンバ関数をセットする
p = T::func();
p = &T::func();
// コールするときは this に渡すインスタンスからよびだす。
class obj;
(obj.*p)();
WARNING
C スタイルの関数ポインタを必要とする場所で、 メンバ関数ポインタを渡すことはできない。
この場合は , 静的メンバ関数、または非メンバ関数を利用する。
シグナルハンドラ
#include< signal.h>
int main() {
// 静的メンバメソッドならば渡すことができる
signal( SIGINT, Test::signalHandler );
}
// グローバル変数経由でわたす インスタンスを取得して メンバ関数をよびだす
スタティックメンバ関数ならば 呼び出し規約は同じになるので
C スタイルの関数ポインタと互換性がある。
■ ステートパターン内で利用する
POINT
動作を表す変数( 動作もまた情報 ) -> ex. stateFunc ( 動作名をいれよう )
-> 応用例( FSM )
class obj
{
protected:
// 状態別 mem 関数の宣言:
typedef void (obj::*stateFunc)(); // objCls の funcPtr( 内部的には obj_stateFunc() )
// それを保持する変数
stateFunc statFn;
private:
// 状態別関数
void state1()
{
printf("do state1\n" );
this->statFn = state2;
}
void state2()
{
printf("do state2\n" );
this->statFn = state1; // cls 名を指定しなくても OK
}
public:
Obj(){
stateFunc() = state1;
}
// update 処理では状態関数を呼ぶだけ
inline void update(){
(this->*statFn)();
}
};
{
switch ( state ){
case STATE1: state1(); break;
case STATE2: state2(); break;
}
}
typedef void (T_Class::*SetParamProc)( const T_Param &val );
typedef const T_Param &(T_Class::*GetParamProc)();
// メンバ関数ポインタ を用いた todo 処理の例
class TestCls
{
/* TestCls の関数か, global関数かを cmp に通知 */
typedef void (TestCls::*Func)();
public:
// todo 一覧
void foo(){ printf("foo" ) };
void bar(){ printf("bar" ) };
TestCls(){
/* TestCls の foo() を渡すことを通知. ( & は任意 ) */
todoList.push_back( &TestCls::goo );
}
vector< Func > todoList;
void action(){
int i;
for( i=0; i< todoList.size(); i++ ){
Func p = todoList[i];
/* 呼ぶときは, どの inst かを通知 */
(this->*p)();
}
};
};
DESC
通常
typedef void (*func)();
cls
typedef void (clsName::*func)(); // void clsName::func() 型の funcPtr
関数 ptr の ads を渡すには ...
通常
void foo(){ ... }
func a = foo;
cls
typedef void (clsName::*func)(); // void clsName::func() 型の funcPtr
void clsName::bbb(){ ... }
関数 ptr を param とする function は
// template cls にすると, 関数の型を以下のように可変にできる
void setClassInfo( T_Class &inst,
SetParamProc set, GetParamProc get, std::string _name = "" );
template< class T_Class, class T_Param> // この cls の この param という
class Test : public TestValue< T_Param> {
private:
};
// 渡し方
// & をつける
POINT
template化 cls は cls 生成の時点で template 指定するが、
mem 関数 は 通常とおり使用できる.
関数ポインタに格納する関数は, [ class 名 ]も同一でなくてはならない。
派生先の関数でも格納は不可能( 派生先しかない情報にアクセスされたらOUT )
この場合は, virtual bcls::todoXXX(){} として枠組みをつくって,
派生先で 必要な処理を overwrite することで OK
もしくは, 各クラスで todo 処理をするふるまい [ actionTodo() ] を作成する
template ということは, class 名の部分を置換してやれば可能。
■ SYNTAX
■ type
■ bool
DESC
C++ の組み込み型
true, false : 予約語 ( 真偽の実装は未検証 )
■ include
新しいC++ header はファイル名を指定せずに、標準識別子のみを指定。
コンパイラによって、ファイル名にmappingする。(こうすることで、本当に必要な定義、prototype が
include される。つまりコンパイラ自身責任をもって選択する。 )
■ new
DESC
new で確保する際の メモリサイズ は new TYPE で決まる
new は Constructor をコールする
■ new new[] delete delete[] を間違えないこと
~QUIT{ printf"quit" ; } とすれば, 1度しか call されない.
int main( void )
{
Foo *p = new Foo[8];
for( unsigned int i=0; i< 8; i++ ){ p[i].srv(); }
delete p; // gcc では core dump
return 0;
}
new[] の際に mem blk に 要素数を埋め込む.
new -> [memtag][mem...] 0x01000000
new[] -> [memtag][pad][要素数][mem...] 0x01000010
-> memtag を取得してみる.
MEMTAG *ptag = static_cast< MEMTAG *>(p) - 1;
■ newをカスタマイズする(placement new)
new は operator new() をオーバーロードすることでカスタマイズできる。
カスタマイズする目的はメモリのアロケートを独自の方式にすること。
new の後に () にパラメータを与えることで、アロケータにパラメータを渡すことになる。
あらかじめ用意したメモリ領域(メモリプール )からアロケートする。
最初に大きめのサイズをまとめて確保して、切り出して使うのがおおまかな流れになる。
// 最初にまとめてメモリ領域を確保
static char memPool[ 1024 ];
void *operator new( size_t sz, char *buf ) {
return sBuf;
}
{
Test *p = new( memPool ) Test();
}
WARNING
返すアドレスは以下の条件を満たす必要がある。
POINT
new を overwride する場合は, 対応する delete も overwride するケースが多い.
Pool からとった場合は, 自分で解放する.
Component したクラスが OperatorNew を定義していても OverwriteNew は呼ばれない
Component したクラス自身を new した際にくる.
class Bar {
Foo f;
}
Bar *b = new Bar(); // f の生成は Bar の生成場所で INIT される.
メモリリークを防ぐには以下のカスタマイズバージョンを使う。
#if _DEBUG
# define NEW new( __FILE__, __LINE__ )
#else
# define NEW new
#endif
WARNING
クラス内で new/delete を定義する場合は, NEW の定義を変えることでコンパイルできなくなる
同様に delete もカスタマイズできる。
独自にアロケートしたメモリを解放する処理をかいておく。
void operator delete( void *ptr ) {
...
//
}
■ new で realloc できるか ?
DESC
できない
POINT
new を使う理由はコンストラクタをコールしてくれること
■ global new
DESC
class 単位ではない new
class 単位が Local
WARNING
STL などはデフォルトで GlobalNew を利用している可能がある.
■ ファイナルクラス
C++ では言語構文として final 指定ができない。
そのためコンパイラレベルではチェックができない。
コメントとして残すことしかできない。
ファイナルメンバ関数の場合は :: によって、仮想関数テーブルをパスできる。
class Derive : public Base {
public:
/* final */ void func();
void f();
};
void f() {
// これはこれ以上オーバーライドされないので
::func();
}
■ RTTI(RunTimeTypeIdentification)
RTTI とは動的型チェックのこと。
実行時に型が正しいかチェックすること。
コンパイラが自動生成するクラスのメタ情報のこと( クラスの型情報を埋め込む )
実行時に型情報を取得できる機能
RTTI を使うと、オブジェクトの型情報を調べて、安全にダウンキャストできる。
USAGE
plm cls 作成
2. /GR ( VC )
3. pDerive = dynamic_cast< derive *>( pBase );
手動でするならば, 次をクラスに持たせる
要はどの種類かわかればいい
clsID
2. prtClsID
メリット
cmp 非依存
2. 余計な cls に RTTI を埋め込む必要なし
3. 組み込む情報を 自由に選べる
■ typeid
実行時のオブジェクトの型をしらべることができる
■ ダイナミックキャストを避ける
ダイナミックキャストを利用すると、利用者側のコードが if - else のロジックで複雑になる。
また新規のクラスが追加すると、拡張が必要になる。
そこで、各クラスの具体的な処理を一般化して基底クラスで仮想関数としてハンドラをつくってしまう。
POINT
if - else, dynamic_cast のコードをみたら、仮想関数による動的コールにまとめてしまう。
プレースホルダーとなるクラスを抽出する。
class Gui {
public :
virtual void draw();
};
次に具体的な処理を仮想関数にかく。
class Button : public Gui {
public :
void draw();
};
class Text : public Gui {
public :
void draw();
};
利用する側は どのクラスか調べる必要もなく、draw() を呼ぶだけですむ。
drawGui( Gui &gui ) {
gui.draw();
}
仮想関数を利用しないと、巨大な if - else ブロックができる。
drawGui() {
if ( gui->getType() == "" ) {
Button *b = (Button *)gui;
b->drawButton();
}
else if ( gui->getType() == "" ) {
}
}
■ ThisPointer
DESC
this は const ではない。変更ができる。
なぜか ? this の中で当然. 自分自身を修正できるから
ので const 関数に渡しても問題なし.
this は CMP としては Foo *const という扱いになる
&this はだめらしい ( '&' に左辺値がありません。)
ie. アドレス演算子(&)のオペランドには左辺値( 代入できる式 )が必要ということ。
( 変数(箱)がないと, その場所を返すことができない )
ClsFoo *p = 0;
this = p; // ERR ( this は左辺値ではない )
POINT
SingleInheritance ( SI )に対しては this は不変.
MultiInheritance ( MI )に対しては this はかわる.
func1 adr 22ef04 // int 1 個分のずれ.
func2 adr 22ef00
func0 adr 22ef00
func3 adr 22ef00
POINT
実は MemFunc へのアクセスは, this ポインタが仕込まれている
memFunc( Foo *this );
そのため内部で VarMem を利用しない場合は NULL を渡しても問題ない。
Foo *f = 0;
f->func(){ printf("test" ) };
■ utility class
DESC
static 共通処理をまとめたもの
static == object のメンバ関数を編集しないもの
■ 継承(inheritance)
■ public.継承
public 継承した場合は 基底クラスへのキャストは安全にできる。
基底クラスの実装内容を再利用して、拡張したクラスを作るには public 継承を使う。
class Gui {
};
class Button : public Gui {
};
] ]]
■ 正しく継承する
より少ない要件で、より多くの振る舞いをすることが正しい。
こうすることで、既存のシステムに新しいものを追加しても破壊されない。
逆に仕様を満たせば、中身は基底クラスと同じでなくてもよい。
■ 実装の継承とインターフェイスの継承
継承は2種類ある。
実装の継承とは クラスの属性、メソッドを基底クラスにまとめてしまうこと。
同じコードがあったときに、関数としてまとめるのと同じ。
複数のクラスに同じような、属性、メソッドがあればそれらをまとめて重複を減らす。
class Gui {
// 大きさ、位置などのパラメータはどの Gui ももっているのでまとめる。
float x, y;
float szx, szy;
// メソッドも共通化できる。
void setPosition( float x, float y );
};
// スライダ固有のパラメータのみを追加する
class Slider : public Gui {
};
インターフェイスの継承は実装を基底クラスからもらうわけではなく
呼び出し方法のみを継承する。
こうしておくと
呼び出し( システム )側が、各インスタンスのクラスごとに場合わけをする必要がなくなる
またインターフェイスだけを知ればよくなるので、実装側のクラスのヘッダが不要になる。
知るべきことが減る。
カプセル化といって、知らなくてもいいことを知らないようにしておくことができる。
class Gui {
virtual void onClick() = 0;
};
class Button : public Gui {
void onClick() {
}
};
■ 継承の使いどころ
if - else で似たようなコードがあったら継承の使いどころ
継承はクラスをグループ化する。
window_event_callback( msg, type ) {
if ( type == E_BUTTON ) {
button->onClick( msg );
}
if ( type == E_BUTTON ) {
text->onClick( msg );
}
if ( type == E_BUTTON ) {
menu->onClick( msg );
}
}
同じようなコードがあるので、基底クラスで仕様だけを決めてしまう。
Gui であるすべてのクラスは onClick( msg ) をもっているというルールをつくる。
class Gui {
virtual void onClick( int msg ) = 0;
};
派生クラスで具体的な処理を仕様にそってつくる。
Button は Gui の一種である。
class Button : public Gui {
void onClick( int msg ) {
}
};
システム側はまとめることができる。クラスごとに場合わけをせずに
基底クラスのインターフェイスにだけアクセスすればよくなる。
window_event_callback( msg, type ) {
gui->onClick( msg );
}
基底クラスの Gui を作る必要がなければ メソッドを = 0 としてしまう。
こうすると
基底クラスで仕様だけを決めるときは、 間違って実体化できないようにしておく。
もし空の実装を用意しておくと、間違ってオーバーライドの忘れなどが発生してしまう。
そこで = 0 として中身がないことをコンパイラに教えて実装忘れがあった場合に怒ってもらう
class Gui {
virtual void onClick( int msg ){}
};
REFERENCE 純粋仮想関数
インスタンスをつくれなくなる。
■ 関数のオーバーロード
派生先で同名のメンバ関数をつくると, オーバーライドはされずに基底クラスの関数が隠される。
そのため 派生先では同名の関数をつくらないようにする。
class Base {
public:
void display();
};
class Derive {
public:
// これは基底の display() を隠す。
void display();
};
Derive d;
func( d, d );
void func( Base &b, Derive &d ) {
// Base::display() が呼ばれる。
b.display();
d.display();
}
■ private継承
private 継承をすると基底クラスの public, protected が 派生クラスの private になる。
protected継承をすると同様に, protected となる。
いつも利用している public 継承は, 基底クラスの public は派生クラスの public になる。
POINT
使いどころは protected アクセスを追加したいとき
また 基底クラスへのポインタに変換できる。
■ アクセス制御
クラス内の情報をアクセス制御をかけるには、クラスのメンバ( 変数, 関数 )を
アクセス制御をつける。
実装であるメンバ変数は private 権限にして,
interface であるメンバメソッドを public にするのが主なアクセス制御になる。
public:
func();
private:
int i;
POINT
private にするということは、変更しても影響を与えないということ。
逆に public な箇所は、公開することになるので、簡単に変更ができないということ。
■ struct.class.の違い
struct は デフォルトレベルが public
class は private
■ 例外(Exception)
C++ にはエラーを処理するための機構として例外というものが用意されている。
C スタイルの関数の戻値としてエラーを返す方法にとって代わる方法のこと。
エラーがプログラムの実行中に発生した場合に
例外オブジェクトというもの (これ自体がクラスのインスタンス)を生成します。
これを例外の送出(スロー)といい
例外オブジェクトを適切な方法で補足(キャッチ)して、 エラー処理をする。
例外をキャッチしたい範囲があれば try ブロックで処理をかいて
catch でリカバリをする処理をかく。
try // 例外の発生し得る範囲
{
throw "例外が発生しました" ; // 例外をスローする
}
catch( const char* str ) // 例外をキャッチする
{
std::cout < < str < < std::endl;
}
catch ブロックには、キャッチできる例外の型を指定する。
catch ( runtime_error ) {
}
// すべての例外型をキャッチする
catch ( ... ) {
}
WARNING
この指定はできない。コンパイルエラーとなる。
各関数はどの例外をなげるか宣言をする。
例外の仕様という。
throw() は例外を投げないことと宣言する。
throw( runtime_error ) は runtime_error 型( またはその派生型 )の例外をなげると説明をする
void func() throw( runtime_error );
■ 例外の使いどころ
例外は無視できない。
例外をなげる場合は、どこかでキャッチをしないとプログラムが終了する。
つまりエラーがあればその場で即死させることができる。
// エラーコードとして返す場合はチェックを無視できる。
ErrorCode fileopen( string &path ) {
if ( path ) {
return E_INVALID_PATH;
}
}
// 投げられた例外はどこかでとらえないとプログラムは終了する。
void fileopen( string &path ) throw( InvalidFilePath );
void fileopen( string &path ) {
if ( path ) {
throw InvalidFilePath( path );
}
}
制御ロジックがエラーハンドルロジックと分離できる
try {
add( a )
add( a )
add( a )
add( a )
}
catch ( ) {
// ここでまとめてエラーハンドル
}
try {
ret = add( a )
if ( ret ) {
}
add( a )
// エラーハンドル
if ( ret ) {
}
add( a )
if ( ret ) {
}
add( a )
}
catch ( ) {
}
■ 例外を捕捉する
全体で例外をキャッチするには main() 内でキャッチをする。
int main() {
try {
}
catch (...) {
}
return 0;
}
もし main で捕捉できない例外があれば、main() の実行前に発生した例外かもしれない。
静的なオブジェクトのコンストラク内で例外をとらえる。
■ 例外処理をカスタマイズする
■ terminate
投げられた例外がキャッチされないと、 terminate() が呼ばれる。
エラーのログをとり, リソースをすべて解放をして abort() をよぶ。
文字とおり最後の処理をする。
set_terminate() によって実装を変更できる。
void my_terminate() throw()
{
printf( "call my_terminate\n" );
char buf[256];
gets( buf );
// エラーのログをとり、フラッシュする。
}
void testfunc() throw( runtime_error )
{
throw runtime_error( "test" );
}
int main() {
set_terminate( my_terminate );
testfunc();
}
POINT
main() 実行前に静的なオブジェクトの初期化が実行される。
このときは 静的なオブジェクトのコンストラクタに set_terminate() をいれると捕捉をできる。
■ unexpected
仕様にない例外をなげると, unexpected() をよばれる。
デフォルトの処理は terminate() をよぶだけ。
set_unexpected() によって内容を変更できる。
unexpected() -> terminate() -> abort()
■ 例外クラスを捕捉する
例外はシステムで定義すみのクラスがある。
それ以外に任意の例外クラスを定義できる。
呼び出し元は自分がエラーハンドルできる例外型を指定できる。
init() {
try {
File->open();
}
catch ( InvalidFileException ) {
// InvalidFileException とその派生クラスの例外だけをとらえる。
// それ以外の例外は無視されて、さらに上の関数へ伝播する
}
}
void main() {
try {
init();
}
catch ( runtime_error ) {
// init() 以下で捕捉されない例外はここに届く。
}
}
POINT
catch で捕獲できる例外オブジェクトは任意の型を補足できる。
派生クラスもキャッチの対象になるので階層構造にしておくと便利。
throw; とするとキャッチした例外を再度スローすることになる。
つまり人に丸投げすることになる。
try - catch をするのは次の条件をみたすとき。
すべての関数よびだしで try - catch をする必要はない
WARNING
リカバリーできない例外をキャッチするのは問題を先送りにしているだけ
try {
}
catch () {
// 何もしない
}
例外がなげられたときは スタック上のオブジェクトは デストラクタが実行される
■ 例外クラスを定義する
POINT
例外は 基底クラスから必ず派生させる。
こうすることで main でキャッチオールブロックを用意できることになる
int main() {
try {
}
catch( exception e ) {
}
}
システムが提供する 例外クラスもある
class InvalidPath : public runtime_error {
public:
InvalidPath() throw();
};
■ コンストラクタの例外
コンストラクタも例外をなげることができる。
コンストラクタで例外を投げるということは
そのオブジェクトが未完全な状態でつくられる。
T::T() throw( runtime_error )
{
}
メンバオブジェクトのコンストラクトに失敗した場合は、
既にコンストラクト済みの他のオブジェクトのデストラクタがよばれる。
■ アンワインドプロセス
例外の実装は longjmp() と同じ。
関数外の場所に制御をうつす。
ただし 例外は スタックを戻してくれる。( unwind process )
スタック上のオブジェクトは デストラクタが実行される。
POINT
ただし /EHsc オプションをつけること
指定しないとデストラクタが実行されない。
■ GetCurrentProcessId
SYNTAX
DWORD GetCurrentProcessId();
DESC
呼び出し側プロセスのプロセス識別子が返す。
Kernel32.lib
■ internal
■ SymGetLineFromAddr
SYNTAX
BOOL SymGetLineFromAddr(
HANDLE hProcess,
DWORD dwAddr,
PDWORD pdwDisplacement,
PIMAGEHLP_LINE Line
);
DESC
指定されたアドレスに対応するソースコードの行を検索する
imagehlp.lib
■ GetFirstStackTrace
■ symgetodulebase
SYNTAX
DWORD SymGetModuleBase(
HANDLE hProcess, // 最初に SymInitialize() へ渡された、プロセスのハンドルを指定する。
DWORD dwAddr // SymLoadModule() を使ってロードしたモジュールのいずれかに含まれている有効な仮想アドレスを指定する。
);
DESC
指定されたアドレスを保持するモジュールのベースアドレスを取得する。
■ SymLoadModule
DWORD SymLoadModule(
HANDLE hProcess,
HANDLE hFile,
PSTR ImageName,
PSTR ModuleName,
DWORD BaseOfDll,
DWORD SizeOfDll
);
DESC
シンボルテーブルをロードします。
■ インライン(Inline)
関数の中身を呼び出し元にインラインする(埋め込む)時に利用する。
C ではマクロでも代用できるが, () をつけないとバグの元になるため Inline を利用するとよい。
POINT
インライン化する目的は主に関数コールのオーバーヘッドをなくして高速化するため。
POINT
コンパイルする cpp 単位で展開するため関数の定義は見えていないといけない。
文字通り[ inline == 埋め込む ] のでヘッダに記述しなければ参照できない。
// test.h
inline int add( int i );
定義部分が見える必要がある。
// test.h
inline int add( int i ) {
return i + 1;
}
int main() {
int i = add( 10 );
}
// 次のように展開される(かもしれない)
int main() {
int i = 10 + 1;
}
■ inline化する
関数を inline するには次の条件をみたすようにする
明示的に inline するように宣言する
inline int add( int i ) {
return i + 1;
}
クラスメソッドの場合はクラスの定義部分でメソッドの定義をかくと暗黙で inline を宣言する。
class Test {
void int add( int i ) {
return i + 1;
}
}
POINT
inline の指示はコンパイラの要求であるので、実際に inline 化するのはコンパイラが決定する。
■ インライン化する
実行コードのサイズはインライン化する関数の大きさによって大きくなったり小さくなったりする。
関数が小さいときは inline 化することで関数呼び出しのオーバーヘッドのコードが減るため小さくなる。
またコンパイラが最適化のために勝手に関数をインライン化することもある。
グローバル最適化をすると発生する。
■ インライン化で減るコード
PageFault がおこる可能性が減る。
ページフォルトとは 仮想メモリOS上でコードがメモリ上にない場合に発生する。
■ インライン化すべき条件
インライン化するには次の条件をみたすかチェックする。
WARNING
開発の段階でインライン化するとヘッダファイルを変更することになるので
大規模な再コンパイルが必要になる。
■ OpaquePointer(オペークポインター)
DESC
不透明なポインタ. ( == 実装が見えない Pointer )
// 不完全型
class MyClass;
後は Pointer として扱えればいい場合のみ利用できる
クラス xxx は 不透明である という
class Test {
public:
// interface
private:
void *data;
class *Impl;
};
// 公開するのは CGparameter
// 実装 struct _CGparameter は見えない
typedef struct _CGparameter *CGparamter;
■ TinyXml
#include "tinyxml.h"
#pragma comment(lib, "./debug/tinyxmld.lib" )
{
TiXmlDocument doc;
TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0" , "" , "" );
TiXmlElement *element = new TiXmlElement( "Hello" );
// エレメントを作成してアトリビュートを設定
TiXmlElement *e = new TiXmlElement( "node" );
e->SetAttribute("name" , "test" );
TiXmlText * text = new TiXmlText( "World" );
// 各ノードを親子づけする
element->LinkEndChild( text );
element->LinkEndChild( e );
doc.LinkEndChild( decl );
doc.LinkEndChild( element );
// 保存する。
doc.SaveFile( "test_write.xml" );
}
XML は次の仕様をみたす必要がある
root ノードはひとつ。
< root>
// 同一の attribute はダメ
< node name="xxx" name="yyy" >
< /root>
// これはダメ
< root/>
■ ReferenceCount(リファレンスカウント)
リファレンスカウントとはオブジェクトの寿命をコントロールする方法のこと。
オブジェクトは誰かから参照されている間は、生き続けて、誰からも参照されなくなったら死ぬ、
という考えで実装する方式のこと。
Java や C# では言語レベルでこの機能を提供するため
明示的に delete を呼ぶ必要がない。
問題となるのは誰がオブジェクトを作成して、破棄するかということ。
通常は オブジェクトを作成した人が破棄する。
しかし、複数の人が参照し始めると、勝手に削除することも危険になる。
そこで、オブジェクト自身が削除するという方式ができた。
■ ReferenceCount
Object *A = new Object();
// 別のポインタからも参照させる。この時点で [ A, B ]の2つから参照される。
{
Object *B = obj;
// B からも見ているよ、ということを伝える。
B->addRef();
// 参照先は責任をもって release() をよんで不要になったことを伝える。
B->release();
}
// ここで誰からも参照されなくなるので、自分自身で自分を消去する。
A->release();
class Object {
Object() : m_cnt(1) {
}
void addRef() {
m_cnt ++;
}
void release() {
m_cnt --;
if ( m_cnt == 0 ) {
delete this;
}
}
private:
int m_cnt;
};
WARNING
リファレンスカウントは 適切に addRef と release が呼ばれないと
delete 忘れや ダングリングポインタ と同じ現象がおきる。
そこで 呼び出し側に操作させずに、少し賢いポインタを渡してあげる。
REFERENCE SmartPointer
REFERENCE SharedPointer
■ SmartPointer(スマートポインタ)
DESC
ネイティブのポインタをラップし、参照カウントがゼロになったときに
ポインタの参照先を自動的にdelete するクラスのこと。
自動削除ポインタのこと。
ポインタのような振る舞いをするクラスのこと。
POINT
解放忘れを防ぎたい場所でする。
destructor のタイミングで自分が管理しているオブジェクトを解放する。
ptr が削除されると, 参照先も自動で削除される ptr
{
// stack 上に SmartPointer がのる。
std::auto_ptr< Test> p( new Test() );
} // スマートポインタオブジェクトが消滅すると Test のインスタンスは削除される。
WARNING
スマートポインタはコピーをするとコピー元は NULL になる。
class Test
{
public:
Test() {
printf( "ctor\n" );
m_data = new char[1024];
sprintf( m_data, "test" );
}
~Test() {
printf( "dtor\n" );
delete m_data;
}
void func() {
printf( "func %s\n" , m_data );
}
private:
char *m_data;
};
{
auto_ptr< Test> p( new Test() );
p->func();
printf( "check Pointer %d\n" , p );
printf( "copy" );
auto_ptr< Test> b = p;
printf( "check Pointer %d" , p );
// p は NULL なのでクラッシュする。
p->func();
b->func();
}
■ スマートポインタの使いどころ
例外や if 文などの複雑なロジックがあった場合でも簡単にかける。
delete 文を省けるというよりも、どんな場合でも解放忘れがないというメリットがある。
POINT
同一スコープでアロケート、デアロケートする処理があったら使いどころ。
Python の with ステートメントみたいなもの
REFERENCE with
REFERENCE Dispose
// 次のような状況でも解放漏れがおきない。
// 解放の処理を明示する必要がない。
{
void func() {
auto_ptr< File> file( new File("d:/test.txt" ) );
try {
if ( ... ) {
// ここでファイルを閉じる
return;
}
else if ( ... ) {
// ここでも
return;
}
} catch () {
// ここでも
}
// ここでも
}
}
POINT
boost::shared_ptr : 内部に参照カウンタをもつため 譲渡できる。
Decoration Pattern のひとつ
WARNING
auto_ptr ではひとつのインスタンスを複数の auto_ptr でシェアできない。
そこでコピーを禁止しておく。
( STL 禁止 == vector< auto_ptr< T> > )
次の場合に問題がおきる。
auto_ptr< Test> p( new Test() );
{
auto_ptr< Test> pp;
pp = p; // 譲渡
// p が指す obj がpp の破棄によって解放
}
// インスタンスは解放すみ
■ 共有できるスマートポインタ
POINT
共有できない欠点を取り除いたものが shared_ptr
生のポインタと比べて次のことができる。
// コンストラクト時に NULL にセットするので未初期化のポインタがなくなる
SmartPointer() : m_ptr( NULL ){}
//
SmartPointer< int> p;
// ここで死んでくれる
*p = 10;
■ SharedPointer(シェアードポインタ)
DESC
SmartPtr のコピーの問題点を改良したポインタクラスのこと。
参照カウンタをもち, どの変数からも参照されない時点で解放する。
ThreadSafe な設計である. ( CntRef の操作時に排他制御がされる. )
-> ( SharedPointer を Thread( Worker ) で共有しなければ OK )
POINT
生のポインタをかえすかわりに, SharedPointer を返せば OK
// ヒープにTestオブジェクトを生成
// この時点で参照カウントはこの時点で1である
boost::shared_ptr< Test> p(new Test()); // p.cntRef = 1;
{
boost:shared_ptr< Test> pp;
// スマートポインタをコピーする(参照カウントが+1される)
pp = p; // pp.refCnt = 2; p.refCnt = 1; // 相手先は cntRef を copy してさらに ++ !
std::cout < < "pp will destruct" < < std::endl;
// このスコープを抜けたときにppはなくなり ppが指しているTestオブジェクト
// への参照カウントは-1されるが 、まだ0ではないためTestオブジェクトは開放されない。
}
// pが指しているTestオブジェクトはまだ開放されていない
ButtonRef Button::create() // RawPointer のかわりに返す。
{
return ButtonRef( new Button() );
}
class Window {
ButtonRef m_button;
};
// 自動的に joint も削除される
delete wnd;
■ shared_ptr
template< class T >
class SharedPtr {
public:
SharedPtr( T *ptr ) : m_ptr(ptr), m_cnt( NULL ){
m_cnt = new int;
*m_cnt = 1;
}
// 死ぬときに参照数をへらす。
// 0になったら 実体を解放する。
~SharedPtr() {
release();
}
// SharedPtr 同士のコピーをする場合は参照数が増やす。
void operator=( SharedPtr &rhs ) {
// 今指している参照を離す。
release();
// 相手と同じ参照をもつ。
m_cnt = rhs.m_cnt;
m_ptr = rhs.m_ptr;
(*m_cnt) ++;
}
void release() {
(*m_cnt) --;
if ( *m_cnt == 0 ) {
delete( m_ptr );
delete( m_cnt );
}
}
private:
// 実体
T *m_ptr;
int *m_cnt;
};
■ デバッグ
■ ポインタ解放忘れを防止する
生のポインタをクラス内で管理することで解放忘れを防止する。
デストラクタを利用することで自動で解放してしまう。
REFERENCE SharedPtr
REFERENCE SmartPointer
■ クラス内で自己診断をする
クラスの正当性をチェックするメソッドをつくる。
assert などと併用すると リリース時にテストを除去できる。
コンストラクタとミューテータで実行する。
また通常のドキュメントとくらべると曖昧性がない。
■ 例外コード ■ ポインタ
POINT
wildpointer はでたらめな位置を書き込み、既存のオブジェクトを破壊する。
ほとんどは運よく、OSがプロテクトする領域を書き込もうとしてプログラムはクラッシュする。
WARNING
ローカルオブジェクトの参照を返すのはワイルドポインタをつくる。
ローカルオブジェクトはスタック上に生成されるため
関数が終了した時点で破棄されることになる。
Vector &func() {
Vector v;
return v;
}
// BAD
const &T min( const T&a, const T&b )
// OK
T min( const T&a, const T&b )
WARNING
リファレンスパラメータの参照を返してはいけない。
const string &s = unsafe( createTmp() )
cout < < s;
WARNING NULL ポインタがすべて 0 bit で埋まる保障はない。( ほとんどの環境では 0 だが )
POINT
const リファレンスを返すのは 余計なコピーをなくして処理を高速化するため
■ CPPでよくある間違い
■ ポインタの2重解放
■ コピーのかわりにmemcpyを使う
POINT
クラスがリファレンス先( ポインタ )をもつ場合は
単なる bit 単位のコピーでは論理的なコピーにならない。
string a = "test" ;
string b = a;
// リファレンス先が共有されるので2重に解放される。
memcpy( &b, &a, sizeof(a) )
realloc も同様にストレージが移動する際は bit 単位のコピーが必要になる。
オブジェクトの配列を利用するときには注意すること。
■ タイプセーフでない関数をつかう
クラスのインスタンスは場合によって仮想関数テーブルのポインタをもつため
"型を無視した操作" は破壊する可能性がある。
class T {
public:
virtual void vfunc(){}
};
void test( T *ptr ) {
// 仮想関数をコールすると落ちる。
ptr->vfunc();
}
T obj;
memset( &obj, 0, sizeof(T) ) ;
test( &obj );
■ コピー操作によってリソースを2重に開放する
class String {
public:
String() : m_data( new char[256] ){}
~String() {
printf( "dtor %X\n" , m_data );
delete m_data;
}
private:
char *m_data;
};
{
String s;
{
// ここで m_data がコピーされるので 2重に delete される。
String st = s;
}
}
そこでポインタを内部にもつクラスはコピーする気がないならばコピーを禁止をしてしまう。
コピーさせないか、DeepCopy をさせる。
private:
String( const String &);
void operator =( const String &);
■ リソース管理
■ ポインタ変数はnewをした後にdeleteする
delete をした後のポインタ変数はすぐに、安全な状態にする
// p の指す先を解放したら
delete p;
// p をリセットする。
p = NULL;
// ここで即死してくれる。
p->func();
■ リファレンスカウント
ヒープにアロケートしたオブジェクトを解放する処理を
自分自身にやらせる。
class Resource {
public:
create();
private:
int m_cnt;
};
// Resource オブジェクトの管理機能つきポインタ
class ResourcePtr {
public:
ResourcePtr( Resource *p )
~ResourcePtr() {
m_ptr->m_cnt --;
if ( m_ptr->m_cnt == 0 ) {
delete m_ptr;
}
}
private:
Resource *m_ptr;
};
REFERENCE コンストラクト方法を制御する
{
ResourcePtr *p = Resource::create();
// 好きなだけコピーできて
p2 = p1;
func( p2 );
// 誰からも参照されなくなったら自動で解放される。
}
■ コピーオンライト
コンピュータプログラミングにおける最適化戦略の一種である。
コンピュータ内部で、
ある程度大きなデータを複製する必要が生じたとき、
愚直な設計では、
直ちに新たな空き領域を探して割り当て、コピーを実行する。
ところが、もし複製したデータに対する書き換えがなければその複製は無駄だったことになる。
そこで
複製を要求されても、コピーをした振りをして、
とりあえず原本をそのまま参照させるが、
ただし、そのままで本当に書き換えてはまずい。
原本またはコピーのどちらかを書き換えようとしたときに、それを検出し、その時点ではじめて新たな空き領域を探して割り当て、コピーを実行する。
これが「書き換え時にコピーする」
基盤となる考え方は、複数の(何らかの)要求者がリソースを要求するときに、
少なくとも当初はそれらの要求を区別する必要がないときに同じリソースを与える、というものである。
これは要求者がリソースを「更新」しようとするまで保持され、「更新」が他者に見えないようにリソースの
個別のコピーを必要になった時点で作成する。
要求者からはこの一連の動きは見えない。
第一の利点は要求者が全く更新しなければ、個別のコピーを作成する必要が生じないという点である。
■ ユーザーコードからポインタを隠す
リソースの管理をクラス内にカプセル化してしまう。
// このスコープを抜ければ、file オブジェクトのデストラクタが解放してくれる。
{
File file( );
file->readLine();
}
リソースが見れると漏れが起きる。
FILE *fp = fopen();
fgets( fp, )
fclose( fp );
■ オブジェクト指向
柔軟性がたかく、変更に対処しやすいコードをつくるための技術とか設計方法のこと。
仮想関数を使うと システム( よびだし )側に変更をせずに、新規要素を追加できる。
■ オブジェクト
コードとしてみれば、あるメモリ領域であって、
設計段階ではプラグラムする対象をクラスとして切り出す単位。
■ コンポジション
コンポジションを使うと既にあるモジュールを組み合わせてプログラムをつくることができる。
■ ポリモーフィズム
基底クラスの参照をつかって、派生クラスのオブジェクトのメソッドを動的に呼び出すこと。
仮想化したインターフェイスを用意しておけば、将来の追加をうけいれることができる。
■ インターフェイス
利用者側のコードからかくこと。テストコードとか、サンプルコードをかく。
そうすると実装を見せずにすむ。
外側からみえるコードを先にかく。それがクラスのインターフェイスになる。
またインターフェイスは利用者が見るところなので
情報は少ない方がよい。
そうすると使う側( つくった本人も含む )も簡単に使い方を覚えることができる。
■ アクセッサを多用しない
set/get とメンバ変数( 実装 )を無闇に関連づけない。
必要なインターフェイスのみを公開すること。
■ 継承をインターフェイスの継承につかう
基底クラスの実装を再利用するために、継承を利用しないで
インターフェイスを継承するために使う。
こうすると 既存のシステムが基底クラスの参照をとって処理をしている部分の
変更がいらないのでシステムが安定する。
POINT
既存コードに影響を与えずに 新しい派生クラスを追加することが目標。
そのためにシステムは 基底クラスの参照をパラメータにとるようにする。
インターフェイスはシステムから見ると、意味があって一貫性のあるまとまりとして切り出すこと。
そうすると派生クラスをつくりやすくなり、
if - else のコードが減る。
POINT
インターフェイスを再利用するということ。
■ ポインタをつかって間接レイヤーをつくる
インターフェイス( 間接レイヤー )を余計につくると、その部分が可変になる。
インターフェイスと実装をわけるとは、ポインタ経由するということ。
■ インターフェイスをきりだす
インターフェイスを切り出すには "類似性" に注目をすること。
配列とか map などのコンテナの実装ではなく、ソートされた要素の集合とみなすと
インターフェイスができる。
ユーザーは実装ではなく、インターフェイスに対してコードをかく。
■ 設計の基本原理
不要な結合をへらすこと
設計をしないと一貫性のない、その場しのぎの決定による偶然の産物になる。
一番変化がありそうな部分に柔軟性を確保すること。
柔軟性を確保すると複雑になるのですべての部分で確保をしないこと。
■ フリーストア
new をつかって確保されるヒープのこと。
■ mutate
オブジェクトを変化させること。
■ throughput(スループット)
処理量, 処理能力, スループット《所定の時間に処理工程を通過する素材の量
■ latency
待ち時間
呼出し時間
データを転送する命令を出してから実際に転送が開始されるまでの時間
POINT
File IO, Network IO が特にレイテンシが大きいので、この空き時間を利用して別のことをする。
■ friend(フレンド)
フレンドには3種類ある
フレンドは次の場合につかう
■ フレンド関数
フレンド関数とはクラスの public 以外のメンバにアクセスできる
非メンバ関数のこと。
friend 関数を宣言するアクセスレベルはどこでもいい。
コンパイラはアクセスレベルの指定は考慮していない。
// 以下のどれでも同じ。
class Vector {
public:
friend ostream &operator < < ( ostream &o, const Vector v );
protected:
friend ostream &operator < < ( ostream &o, const Vector v );
private:
friend ostream &operator < < ( ostream &o, const Vector v );
};
■ フレンド関数のつかいどころ
本質的にはメンバ関数とかわらない。( メンバ関数は public 以外にアクセスできるから )
フレンド関数にするには、利用者側のコードが直感的になるようになるときに使う。
そのため本来、メンバ関数であるべきものを、フレンド関数にするといい。
class Vector {
public:
friend ostream &operator < < ( ostream &o, const Vector v );
};
ostream &operator < < ( ostream &o, const Vector v ) {
return o < < v.x < < " " < < v.y < < " " < < v.z < < endl;
}
{
Vector v;
cout < < v < < endl;
}
■ フレンド関数を仮想関数にする
フレンド関数は仮想関数にできないので、フレンド関数側で、仮想関数をよびだすことで実現する。
class Gui {
public:
friend ostream & operator< < ( ostream &o, const Gui &gui );
protected:
// 実体
virtual void print( ostream &o ) const = 0;
};
ostream & operator< < ( ostream &o, const Gui &gui ) {
gui.print();
}
class Button : public Gui {
protected:
virtual void print( ostream &o ) const {}
};
■ フレンドクラス
あるクラスが別のクラスに private アクセスを許す場合は次のようにかく
// Base クラスは T からのアクセスを許可する
class B ase {
frie nd T;
};
派生クラスは friend 関係を "継承しない" 。
( 許可すると派生してしまえばすべてアクセスできることになるから )
class Derive : public Base {
};
■ フレンドクラスのつかいどころ
クラスの特徴をユーザーからは隠したいが、特定の関連クラスにだけ公開したい場合に使う。
プライベートクラスとして使える。
プライベートクラスは実装のためだけに使うクラス。
これを使うと、名前の重複を防ぐことができる。
class LinkList {
// クラス Node は LinkList 固有のため内部のスコープにしてプライベートにしてしまう。
class Node {
friend Node;
};
}
■ 仮想関数
基底クラスのポインタをつかってメソッドを呼ぶと
そのインスタンスのメソッドが呼ばれる
以下のような処理をコンパイラがしてくれる。
インスタンスの中には自分がどのクラスであるかという情報が入っている。
dynamic_cast はこれを利用している。
class Base {
void func() {
if ( m_type == 'A' ) {
A *a = reinterpret_cast< A*>(this);
a->func();
}
else ( m_type == 'B' ) {
A *b = reinterpret_cast< B*>(this);
b->func();
}
}
}
■ COM
COM とは Windows 標準の共通のオブジェクトのコンポーネント
COM をつくると Windows 環境で再利用可能なコンポーネントをつくることができる。
言語に依存しない
バイナリ形式
C++ コンパイラがすることを自前でする。
■ 名前
COM は名前を一意にもつ。
これを GUID といい、世界中で開発されたコンポーネントとの重複をふせぐ。
GUID は 128bit の数値ID
which guidgen.exe をつかって guid を生成する。
コンピュータのネットワークカードの uniq id を使って生成をする。
■ COM.の問題点
わかりずらい
■ インターフェイスとは
実装をもたない仕様のこと
C++ でいえば関数ポインタテーブルのこと。
純粋仮想関数の集合
■ IUnknown
すべてのインターフェイスの基底くらす
■ COMクラスとCOMインターフェイスは全くの別物
■ IUnkowon.インターフェイス
インターフェイスの基底
3つの method をもつ
class IUnkowon {
public:
virtual HRESULT QueryInterface( REFID rid, void *ppv ) = 0;
virtual unsigned long AddRef() = 0;
virtual unsigned long Releaes() = 0;
};
POINT
リファレンスカウントは オブジェクトの寿命を管理する方法のひとつ
■ COMInterfaceの定義
COMInterface をつくるには次の方法がある。
■ C++
marshal(整列させる)
C++ 以外の呼び出し元から呼べなくなる。
■ マクロをつかう
言語, OS の差異を吸収できる。
■ MIDL
言語に依存せずに インターフェイスを定義できる
プロキシとスタブを自動生成する。
このため プロセス間通信で必要な処理を自動でしてくれる。
別プロセスの関数を呼ぶということ。
別プロセス間での通信処理をしてくれる。
■ COM.のエラー
1 bit エラー | OK
15 bit エラーコード
16 bit リターンコード
問題点は
関数の呼び出し側が常にエラーコードをチェックする必要があること。
REFERENCE Exception
■ バージョン
COM はバージョンを管理しない
インターフェイスを変更すれば、それは新しい ID をもつインターフェイスとなる
■ COMの概要
COMInterface
特定の COMClass とは完全に独立している。
COM クラス
( COMInterface を実装したもの )
コードの本体。
CLSID をもつ
COM オブジェクト
COM クラスのインスタンスのこと。
StandardInterface
マイクロソフトがプリセットで提供する COM のインターフェイスのこと。
CustomInterface
自作した COM interface のこと。
■ COMClass.をつくる
C++ で COMClass をつくるには次のようにする。
class CoStack : public IStack {
// IUnknown
STDMETHOD ( QueryInterface )( REFIID riid, void *ptr );
// IStack
};
STDMETHODIMP CoStack::QueryInterface( REFIID riid, void *ptr ) {
if ( riid = IID_IUNKOWN ) {
}
else if ( riid = IID_ISTACK ) {
*ptr = this;
}
else {
*ptr = NULL;
}
AddRef();
// ステータスコードを返す
return S_OK;
}
■ オートメーション機構
JScript, VBScript
-------------------------
オートメーション機構 ( Automation )
-------------------------
COM < ---- C, C++ からアクセス
-------------------------
スクリプトツールが 低レベルの COM を利用できないときに使う。
スクリプトと COM (バイナリ)の糊付けのために使う。
スクリプトから利用してもらうには、IDL をつかって interface を記述してあげる。
WARNING
型は Automation 互換型である必要がある。
各プロパティとメソッドは DISPID をもつ。
properties:
[id(1), propget] boolean Empty;
methods:
[id(2)] void Push( long value );
[id(3)] long Pop();
実際によばれるのは IDispatch インタフェイスによって機能する。
interface IDispatch : public IUnkowon{
// 名前(文字列)から DISPID をひく
virtual HRESULT GetIDsOfNames( .... ) = 0;
// プロパティへのアクセスをする
// 一度取得した DISPID は保存される。
virtual HRESULT Invoke();
};
POINT
Invoke は printf に似ている。 interpreter として機能する。
つまり小さなプログラムをかいている。
COMObject のうち
DispatchInterface を提供するものを AutomationServer という。
これを使う コントローラは AutomationController という。
COMObject
は DLL ( InProcessServer ) または EXE( OutOfProcessServer )として実装することもできる。
■ DualInterface
DispatchInterface と VtableInterface の両方をもつもの
呼び出し元は好きな方を選択をできる。
--------------------------------
COMInterface
DualInterface
--------------------------------
■ MetaData
インターフェイスとクラスの情報のこと。 ( reflection )のこと。
あるクラスに対して、どんな機能をもつか調べることができること。
TypeLibrary には COMInterface , COMClass の定義が含まれている。
■ COM.と.C++の違い
C++ : レジストリを利用しない
COM : クラス , interface, TypeLibrary といった静的な情報を保存する。
C++ : new で生成したオブジェクトは delete をつかって破棄をする。
COM : Object 自身を削除することはない。 interface ポインタが不要になったら削除をする。
COM オブジェクト自身が削除をする。
■ TypeLibrary
コンパイルされた IDL ファイルのこと。
プログラムからアクセスできる。
これによって どんな情報をもつか わかることできる。
LIBID という ID をもつ。
スクリプト言語で役にたつ機能
■ ディスパッチインターフェイス
JScript と COM を連携するために使う
プロパティとメソッドを公開するには activeX コントロールにする必要がある。
■ ActiveX
コントロールとは 1つの プログラムのコンポーネントのこと。( GUI の意味ではない )
VBX ---> OLE ---> ActiveX
ActiveX は ディスパッチインターフェイスを通して
プロパティ, メソッド, イベントを 公開する。
イベントとは
名前、パラメータ 戻り値 をもつメソッドのようなもの。
ActiveX のボタンがクリックされると イベントが発生する。
コントロールコンテナによって実装される。
各イベントはメッセージキューにいれられるので非同期に処理される。
ActiveXControl の特徴
DispatchInterface をとおして property, method, event を公開する。
( AutomationObject のこと )
■ コントロールコンテナ
ActiveX コントロールを含むことができるコンポーネントのこと。
コントロールの存在を検索したりするのが仕事。
コントロールカテゴリによって レジストりに分類される。
■ COM.の使い方
// 必要な CLSID は COM の作成者側が提供する。
// 初期化をする( COM ライブラリをロードする )
HRESULT hr = CoInitialize( NULL );
{
// COM オブジェクトを CLSID を指定して作成する
HRESULT hr = CoCreateInstance( CLSID_CoStack, NULL, IID_IUnkown, ptr );
// 指定したインターフェイスを取得する。なければ NULL が返る。
hr = ptr->QueryInterface( IID_STACK, ptr_stack );
}
// 後処理をする
CoUninitialize();
■ クラスオブジェクト(ClassObject)
すべての COMClass は ClassObject をもつ。
これはメタクラスとして機能する。
クラスオブジェクトは システムレジストリに登録されて、オブジェクト生成に利用される。
すべての COMClass と ClassObject は COMサーバ内にある。
COMサーバ( DLL, EXE )内にある、実行可能形式なコードのこと。
IClassFactory を実装する。
class CoStackClassObject : public IClassFactory {
public:
STDMETHOD(CreateInstance)( IUnkowon *outer, REFIID riid, void *ptr );
};
STDMETHOD(CreateInstance)( IUnkowon *outer, REFIID riid, void *ptr )
{
if ( outer != NULL ) {
CoStack *p = new CoStack();
return p;
}
}
■ 言語に非依存
COMInterface は メモリレイアウトが バイナリ形式であるため
言語に依存しない。
COMInterface は関数ポインタのテーブルにすぎない。
C++
クラス定義をみて, コンパイラが仮想関数テーブルを作成してくれる。
C
struct と 関数へのポインタを使って実現できる。
■ 位置に非依存
別プロセスにある場合でも proxy, stub を経由して実行される。
よびだし元 ---> proxy ----> stub ---> COMClass
proxy オブジェクトがパラメータを送信する。
stub オブジェクトがパラメータを受け取り、 COMClass の関数をよびだす。
その結果を proxy に送る。
■ DCOM
RPC( Remote Procedure Call )をベースにしている。
■ COMの使いどころ
呼び出し元と COMClass が別の言語でかかれるときに使う。
■ CORBA
異なるコンピュータ, 言語でかかれたオブジェクト同士間で利用できる。
IDL は実装言語ではなく仕様言語
Proxy パターンを利用して, 言語非依存を実現している。
機種依存をしない形のデータ型をもつ。
CORBA::long は 4byte
sizeof( long ) は機種依存のため 4byte とは限らない。
■ marshaling
marshaling とは異なるマシン間で呼び出しをする場合に
パラメータを適切なバイナリ表現形式にすること。
IIOP