C++ Coding Standards
![]() | C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス 浜田 光之、ハーブ サッター 他 (2005/10) ピアソンエデュケーション この商品の詳細を見る |
EffectiveシリーズやExceptionalシリーズのトピックスを集めて簡潔にまとめたような書籍。
全てのC++プログラマが手元に置いて、日常的に読み返して欲しい一冊。Exceptional C++のHerb Sutter氏とModern C++ DesignのAndrei Alexandrescu氏の共著によるC++のCoding Standardsです。
RAIIイディオム
Problem
『後始末』を確実に行うには、どのようにすれば良いでしょうか?
Solution
何らかの処理を行う前に初期化を行い、処理が終わった後には『後始末』が必要となることがしばしばあります。標準ライブラリで言えばstd::fopen/std::fcloseの例などが挙げられます。
問題の一つはメンテナンス性の悪さです。例えば、処理の途中のreturnステートメントの前に必ず、std::fcloseを呼び出すという『後始末』を記述する責務がプログラマに課せられます。これでは修正に対して、とても脆弱なコードと言えます。
次に、例外安全性に関する問題があります。例えば、処理の途中で例外が発生した場合に『後始末』の実行が省かれてしまい、リソースリークが発生してしまいます。
それでは、どのような解決策があるでしょうか。最もエレガントな解決策は、コンストラクタとデストラクタを用いてリソースを管理するテクニックです。
これがResource Acquisition Is Initialization Idiomと呼ばれるイディオムです。
このRAIIイディオムはファイルのopen/closeに限らず、共有リソースのlock/unlockやnew/deleteなどの動的なメモリアロケーションなどに対しても有効です。その中でもnew/deleteに特化したものが、標準ライブラリのstd::auto_ptrやstd::tr1::shared_ptrに代表されるスマートポインタ(smart pointer)です。
関連記事
・RAIIイディオム
・RAIIイディオム - Part2
『後始末』を確実に行うには、どのようにすれば良いでしょうか?
Solution
何らかの処理を行う前に初期化を行い、処理が終わった後には『後始末』が必要となることがしばしばあります。標準ライブラリで言えばstd::fopen/std::fcloseの例などが挙げられます。
void func(const char* file)
{
std::FILE* fp = std::fopen(file, "r");
// 何らかの処理を行う
std::fclose(fp);
}
さて、上記のコードにはいくつか問題点があります。問題の一つはメンテナンス性の悪さです。例えば、処理の途中のreturnステートメントの前に必ず、std::fcloseを呼び出すという『後始末』を記述する責務がプログラマに課せられます。これでは修正に対して、とても脆弱なコードと言えます。
次に、例外安全性に関する問題があります。例えば、処理の途中で例外が発生した場合に『後始末』の実行が省かれてしまい、リソースリークが発生してしまいます。
それでは、どのような解決策があるでしょうか。最もエレガントな解決策は、コンストラクタとデストラクタを用いてリソースを管理するテクニックです。
class FileResource {
public:
explicit FileResource(std::FILE* fp) : fp_(fp) {}
~FileResource() { if (fp_) std::fclose(fp_); }
std::FILE* get() { return fp_; }
private:
std::FILE* fp_;
};
void func(const char* file)
{
FileResource fileResource(std::fopen(file, "r"));
// 何らかの処理を行う
}
リソースを管理する責務をクラスオブジェクトに託すことで、オブジェクトのライフタイムが終わる時に、そのオブジェクトが自働的に『後始末』を行ってくれます。これがResource Acquisition Is Initialization Idiomと呼ばれるイディオムです。
このRAIIイディオムはファイルのopen/closeに限らず、共有リソースのlock/unlockやnew/deleteなどの動的なメモリアロケーションなどに対しても有効です。その中でもnew/deleteに特化したものが、標準ライブラリのstd::auto_ptrやstd::tr1::shared_ptrに代表されるスマートポインタ(smart pointer)です。
関連記事
・RAIIイディオム
・RAIIイディオム - Part2
Modern C++ Design
![]() | Modern C++ Design―ジェネリック・プログラミングおよびデザイン・パターンを利用するための究極のテンプレート活用術 アンドレイ アレキサンドレスク (2001/12) ピアソンエデュケーション この商品の詳細を見る 正誤表はこちら |
C++はマルチパラダイムをサポートしたプログラミング言語であり、本書はジェネリック・プログラミングに関する書籍である。
ジェネリック・プログラミング以外のパラダイム(例えばオブジェクト指向)に関する書籍は多くあるがジェネリック・プログラミングに関する書籍(特に日本語の書籍)は極めて少なく、このAndrei Alexandrescu氏の著書はジェネリック・プログラミングの深遠を知る上でとても重要な書籍である。
STLコンテナにリファレンスを格納させる
Problem
std::vectorなどのSTLコンテナに格納されるオブジェクトの型にリファレンスを指定したい場合は、どのようにすれば良いでしょうか?
Solution
標準ライブラリのSTLコンテナに格納されるオブジェクトの型は、CopyConstructibleかつAssignableを満たしている必要があります。std::auto_ptrなどは、このコンテナ要件を満たしていないためコンテナに格納できません。リファレンスの場合も同様に、このコンテナ要件を満たさないためにコンテナに格納できません。
しかし、C++0xに含まれるテンプレートクラスstd::tr1::reference_wrapperを用いれば、CopyConstructible要件とAssignable要件を満たすリファレンスのラッパ型を具現化できます。
std::vectorなどのSTLコンテナに格納されるオブジェクトの型にリファレンスを指定したい場合は、どのようにすれば良いでしょうか?
Solution
標準ライブラリのSTLコンテナに格納されるオブジェクトの型は、CopyConstructibleかつAssignableを満たしている必要があります。std::auto_ptrなどは、このコンテナ要件を満たしていないためコンテナに格納できません。リファレンスの場合も同様に、このコンテナ要件を満たさないためにコンテナに格納できません。
しかし、C++0xに含まれるテンプレートクラスstd::tr1::reference_wrapperを用いれば、CopyConstructible要件とAssignable要件を満たすリファレンスのラッパ型を具現化できます。
#include <iostream>
#include <algorithm>
#include <vector>
#include <tr1/functional>
int main()
{
int object0 = 0;
int object1 = 1;
std::vector<std::tr1::reference_wrapper<int> > v;
v.push_back(std::tr1::ref(object0));
v.push_back(std::tr1::ref(object1));
// コンテナの要素はオブジェクトのリファレンスなので、アドレスと値が等しい。
std::cout << "0: object0(" << &object0 << ") = " << object0 << ", v[0](" << &v[0].get() << ") = " << v[0] << std::endl;
std::cout << "1: object1(" << &object1 << ") = " << object1 << ", v[1](" << &v[1].get() << ") = " << v[1] << std::endl;
std::cout << std::endl;
// オブジェクトをswapすると、コンテナの要素の値もswapされる。
std::swap(object0, object1);
std::cout << "0: object0(" << &object0 << ") = " << object0 << ", v[0](" << &v[0].get() << ") = " << v[0] << std::endl;
std::cout << "1: object1(" << &object1 << ") = " << object1 << ", v[1](" << &v[1].get() << ") = " << v[1] << std::endl;
std::cout << std::endl;
// コンテナの要素の値をswapすると、オブジェクトもswapされる。
std::swap(v[0].get(), v[1].get());
std::cout << "0: object0(" << &object0 << ") = " << object0 << ", v[0](" << &v[0].get() << ") = " << v[0] << std::endl;
std::cout << "1: object1(" << &object1 << ") = " << object1 << ", v[1](" << &v[1].get() << ") = " << v[1] << std::endl;
std::cout << std::endl;
// コンテナの要素をswapすると、要素の位置がswapされる。
std::swap(v[0], v[1]);
std::cout << "0: object0(" << &object0 << ") = " << object0 << ", v[0](" << &v[0].get() << ") = " << v[0] << std::endl;
std::cout << "1: object1(" << &object1 << ") = " << object1 << ", v[1](" << &v[1].get() << ") = " << v[1] << std::endl;
std::cout << std::endl;
return 0;
}
実行結果はこうなります。
0: object0(0xaf968430) = 0, v[0](0xaf968430) = 0 1: object1(0xaf96842c) = 1, v[1](0xaf96842c) = 1 0: object0(0xaf968430) = 1, v[0](0xaf968430) = 1 1: object1(0xaf96842c) = 0, v[1](0xaf96842c) = 0 0: object0(0xaf968430) = 0, v[0](0xaf968430) = 0 1: object1(0xaf96842c) = 1, v[1](0xaf96842c) = 1 0: object0(0xaf968430) = 0, v[0](0xaf96842c) = 1 1: object1(0xaf96842c) = 1, v[1](0xaf968430) = 0
Exceptional C++ Style
![]() | Exceptional C++ Style―40のクイズ形式によるプログラム問題と解法=スタイル編 浜田 光之、ハーブ サッター 他 (2006/09) ピアソンエデュケーション この商品の詳細を見る |
Herb Sutter氏のExceptional C++の続編です。
Exceptional C++の原著が1999年刊行ということで、C++が標準化されて間もないころの著書でしたが、こちらの原著は2004年刊行ということで、より洗練された内容になっています。
このHerb Sutter氏のExceptionalシリーズは、Scott Meyers氏のEffectiveシリーズと補間しあうような関係にあり、Effectiveシリーズと併せて全てのC++プログラマが読むべき書籍です。
Exceptional C++
![]() | Exceptional C++―47のクイズ形式によるプログラム問題と解法 浜田 光之、ハーブ サッター 他 (2000/11) ピアソンエデュケーション この商品の詳細を見る |
Scott Meyers氏のEffective C++、Effective STLの次に重要な書籍を挙げるならHerb Sutter氏のExceptionalシリーズです。
このExceptional C++は例外安全性について詳しく述べられています。例外安全性に関してはC++に限らず、例外をサポートする現代的なプログラミング言語の全てにおいて、注意を払う必要がある事柄だと思います。
仮想関数を用いないStrategyパターン
Problem
Strategyパターンは便利なデザインパターンですが、他の多くのデザインパターンと同様に実装には仮想関数を用いるのが一般的です。 しかし、様々な理由から、仮想関数を用いたくない場合があります。仮想関数を用いないStrategyパターンを実装してみてください。
Solution
デザインパターンのバイブルであるオブジェクト指向における再利用のためのデザインパターンによるとStrategyパターンとは、
アルゴリズムを交換可能にすることができれば良いわけで、その実装方法として仮想関数を用いることが一般的となっています。 つまり実装方法として、仮想関数(実行時ポリモーフィズム)を用いることが唯一の手段ではありません。例えば、関数ポインタでも実装できそうです。 しかし、C++では関数ポインタを用いるよりも遥かにエレガントで柔軟な実装方法があります。
それはテンプレート(コンパイル時ポリモーフィズム)です。
関数ポインタは関数のシグネチャーが厳密に一致している必要がありますが、 std::tr1::function/std::tr1::bindを用いれば関数だけでなく、関数オブジェクトやメンバ関数を交換可能なアルゴリズムとして扱うことができます。
実装例はこうなります。
1. Effective C++ : 35項 仮想関数の代わりになるものを考えよう
Strategyパターンは便利なデザインパターンですが、他の多くのデザインパターンと同様に実装には仮想関数を用いるのが一般的です。 しかし、様々な理由から、仮想関数を用いたくない場合があります。仮想関数を用いないStrategyパターンを実装してみてください。
Solution
デザインパターンのバイブルであるオブジェクト指向における再利用のためのデザインパターンによるとStrategyパターンとは、
アルゴリズムの集合を定義し、各アルゴリズムをカプセル化して、それらを交換可能にする。とあります。
アルゴリズムを交換可能にすることができれば良いわけで、その実装方法として仮想関数を用いることが一般的となっています。 つまり実装方法として、仮想関数(実行時ポリモーフィズム)を用いることが唯一の手段ではありません。例えば、関数ポインタでも実装できそうです。 しかし、C++では関数ポインタを用いるよりも遥かにエレガントで柔軟な実装方法があります。
それはテンプレート(コンパイル時ポリモーフィズム)です。
関数ポインタは関数のシグネチャーが厳密に一致している必要がありますが、 std::tr1::function/std::tr1::bindを用いれば関数だけでなく、関数オブジェクトやメンバ関数を交換可能なアルゴリズムとして扱うことができます。
実装例はこうなります。
#include <iostream>
#include <sstream>
#include <tr1/functional>
struct ConcreteStrategyA {
int operator()(char* argument) const
{
std::cout << "ConcreteStrategyA::operator()(const char* argument) : " << std::ends;
int result = 0;
std::stringstream(argument) >> result;
return result;
}
};
short ConcreteStrategyB(const char* argument)
{
std::cout << "ConcreteStrategyB(char* argument) : " << std::ends;
short result = 0;
std::stringstream(argument) >> result;
return result;
}
struct ConcreteStrategyC {
unsigned short method(void* argument) const
{
std::cout << "ConcreteStrategyC::method(void* argument) : " << std::ends;
unsigned short result = 0;
std::stringstream(static_cast<const char*>(argument)) >> result;
return result;
}
};
class Context {
public:
typedef std::tr1::function<int (char*)> function_type;
typedef function_type::result_type result_type;
typedef function_type::argument_type argument_type;
explicit Context(function_type strategy = ConcreteStrategyA())
: strategy_(strategy) {}
result_type interface(argument_type n)
{ return strategy_(n); }
private:
function_type strategy_;
};
int main()
{
// デフォルト引数により関数オブジェクトを用いる
Context contextA;
std::cout << contextA.interface("100") << std::endl;
// 関数を用いる
Context contextB(ConcreteStrategyB);
std::cout << contextB.interface("100") << std::endl;
// メンバ関数を用いる
Context contextC(std::tr1::bind(&ConcreteStrategyC::method,
ConcreteStrategyC(),
std::tr1::placeholders::_1));
std::cout << contextC.interface("100") << std::endl;
return 0;
}
実行結果はこうなります。
ConcreteStrategyA::operator()(const char* argument) : 100 ConcreteStrategyB(char* argument) : 100 ConcreteStrategyC::method(void* argument) : 100Notes
1. Effective C++ : 35項 仮想関数の代わりになるものを考えよう
![]() | Effective C++ 原著第3版 スコット・メイヤーズ (2006/04/29) ピアソン・エデュケーション この商品の詳細を見る |
Effective STL
![]() | Effective STL―STLを効果的に使いこなす50の鉄則 スコット メイヤーズ (2002/01) ピアソンエデュケーション この商品の詳細を見る |
Scott Meyers氏のEffectiveシリーズのSTL本です。
Effective C++はC++全般を取り扱っていますがSTLに関する内容は僅かです。STLに関してはEffective STLとして独立した書籍にまとめられています。STLを使用するプログラマ(つまり全てのC++プログラマ)必読の一冊です。






