Effective C++
![]() | Effective C++ 原著第3版 スコット・メイヤーズ (2006/04/29) ピアソン・エデュケーション この商品の詳細を見る |
エレガントなC++を書くためのスタイルガイドで、数多くあるC++書籍の中で最も重要であり、全てのC++プログラマが読まなければならない書籍が、このScott Meyers氏のEffective C++です。
第3版ではTR1(C++0x)やboostに関する内容を含んでおり、旧版を所有している方も読み直す必要がある。
Algorithm vs Loop
Problem
配列の総和を求める場合、Algorithmを用いるのとLoopを用いるのではどちらが良いでしょうか?
パフォーマンスとメンテナンス性の高いAlgorithmを使いましょう。あなたは、「Loopの方が分かりやすくメンテナンスしやすい」と言うかもしれませんが、それは誤りです。
何故なら、Algorithmはどのような動作を行うかは明確に分かりますが、Loopがどのような動作をするかはそれぞれのLoopのコードを読み解かないと分かりません。Loopで明確なのはループ処理を行っているという事実だけで、実際の処理内容は読み解かなければなりません。
また、Algorithmを使わずLoopを書くということは、std::memcpy()やstd::strcpy()やstd::strlen()等を使わずにLoopを書くということと同じことです。
あなたはそれでもAlgorithmを使わずにLoopを書きますか?
Notes
1. 東方算程譚 - 「難しい」ってどーゆーことなんだろう
2. Effective STL - 第43項 独自に作成したループよりアルゴリズムの呼び出しを優先して使おう
配列の総和を求める場合、Algorithmを用いるのとLoopを用いるのではどちらが良いでしょうか?
// Algorithm int array[N]; int sum = std::accumulate(array, array+N, 0);
// Loop
int array[N];
int sum = 0;
for (int i = 0; i < N; ++i) {
sum += array[i];
}
Solutionパフォーマンスとメンテナンス性の高いAlgorithmを使いましょう。あなたは、「Loopの方が分かりやすくメンテナンスしやすい」と言うかもしれませんが、それは誤りです。
何故なら、Algorithmはどのような動作を行うかは明確に分かりますが、Loopがどのような動作をするかはそれぞれのLoopのコードを読み解かないと分かりません。Loopで明確なのはループ処理を行っているという事実だけで、実際の処理内容は読み解かなければなりません。
また、Algorithmを使わずLoopを書くということは、std::memcpy()やstd::strcpy()やstd::strlen()等を使わずにLoopを書くということと同じことです。
あなたはそれでもAlgorithmを使わずにLoopを書きますか?
Notes
1. 東方算程譚 - 「難しい」ってどーゆーことなんだろう
2. Effective STL - 第43項 独自に作成したループよりアルゴリズムの呼び出しを優先して使おう
![]() | Effective STL―STLを効果的に使いこなす50の鉄則 スコット メイヤーズ (2002/01) ピアソンエデュケーション この商品の詳細を見る |
配列 array
Problem
配列をSTLコンテナのように扱いたい。
Solution
もともと配列をalgorithmに渡すことは可能ですが、begin()/end()などのメンバ関数は当然利用できません。
しかし、std::tr1::arrayを用いれば配列をSTLコンテナのように扱えます。
正確にはarrayがスタックに積まれる固定長コンテナということになります。
配列をSTLコンテナのように扱いたい。
Solution
もともと配列をalgorithmに渡すことは可能ですが、begin()/end()などのメンバ関数は当然利用できません。
しかし、std::tr1::arrayを用いれば配列をSTLコンテナのように扱えます。
正確にはarrayがスタックに積まれる固定長コンテナということになります。
#include <iostream>
#include <algorithm>
#include <tr1/array>
int main()
{
typedef std::tr1::array<int, 10> container;
container c;
for (container::size_type pos = 0; pos < c.size(); ++pos) {
c[pos] = pos;
}
std::copy(c.begin(), c.end(),
std::ostream_iterator<container::value_type>(std::cout, ", "));
std::cout << std::endl;
return 0;
}
実行結果はこうなります。0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
std::stringの大文字/小文字変換
Problem
どのようにすれば、std::stringの大文字/小文字変換を行えますか?
Solution
std::transform()とstd::toupper()/std::tolower()を用いて簡単に書けます。
通常は上記の書き方で問題ありませんが、これがコンパイルエラーとなる場合があります。
例えばGCC 4.1.1で<locale>や<iomanip>をインクルードしている場合、以下のようなコンパイルエラーが発生します。
どのようにすれば、std::stringの大文字/小文字変換を行えますか?
Solution
std::transform()とstd::toupper()/std::tolower()を用いて簡単に書けます。
std::string str("hello, world");
std::transform(str.begin(), str.end(), str.begin(), std::toupper);
以上…ちょっと待ってください!通常は上記の書き方で問題ありませんが、これがコンパイルエラーとなる場合があります。
例えばGCC 4.1.1で<locale>や<iomanip>をインクルードしている場合、以下のようなコンパイルエラーが発生します。
g++ -c -pipe -Wall -W -Werror -g -o main.o main.cpp main.cpp: In function ‘int main()’: main.cpp:10: error: no matching function for call to ‘transform(__gnu_cxx::__normal_iterator<char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, __gnu_cxx::__normal_iterator<char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, __gnu_cxx::__normal_iterator<char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, <unresolved overloaded function type>)’そこで、本来のシグネチャー
int toupper(int) int tolower(int)を明示的に指定すればコンパイルが行えます。
std::transform(str.begin(), str.end(), str.begin(), (int (*)(int))std::toupper);
Exceptional C++ Style - 8 テンプレートをfriendに
Problem
Exceptional C++ Styleの訳本が出版されました。
「8 テンプレートをfriendに」では、GCCが他のコンパイラに比べて標準規格対応が劣っていましたが、最新のgccではどうでしょうか?
Solution
Exceptional C++ Styleの原著が2004年発刊と少しばかり古いので「8 テンプレートをfriendに」におけるコンパイラのバージョンが古かったりします。
そこで問題になっていたgccに関して、最新のGCC 4.1.1でテストしてみましょう。
まず例8-1から。
次に例8-2。
結局、結論は「例8-2」を使おう!ということになります。
Notes
1. Exceptional C++ Style - 8 テンプレートをfriendに
Exceptional C++ Styleの訳本が出版されました。
「8 テンプレートをfriendに」では、GCCが他のコンパイラに比べて標準規格対応が劣っていましたが、最新のgccではどうでしょうか?
Solution
Exceptional C++ Styleの原著が2004年発刊と少しばかり古いので「8 テンプレートをfriendに」におけるコンパイラのバージョンが古かったりします。
そこで問題になっていたgccに関して、最新のGCC 4.1.1でテストしてみましょう。
まず例8-1から。
// 例8-1:再度。
//
namespace boost {
template<typename T> void checked_delete(T* x) {
// ...その他の処理...
delete x;
}
}
class Test {
~Test() {}
friend void boost::checked_delete(Test* x); // 元のコード。
};
int main() {
boost::checked_delete(new Test);
}
結果は、g++ -c -pipe -Wall -W -Werror -g -o main.o main.cpp main.cpp:12: error: ‘void boost::checked_delete(Test*)’ should have been declared inside ‘boost’ main.cpp: In function ‘void boost::checked_delete(T*) [with T = Test]’: main.cpp:16: instantiated from here main.cpp:11: error: ‘Test::~Test()’ is private main.cpp:6: error: within this context make: *** [main.o] エラー 1ということでGCC 4.1.1でもコンパイルできませんでした。
次に例8-2。
// 例8-2:friend宣言の別の方法。
//
namespace boost {
template<typename T> void checked_delete(T* x) {
// ...その他の処理...
delete x;
}
}
class Test {
~Test() {}
friend void boost::checked_delete<>(Test* x); // 代替案。
//friend void boost::checked_delete<Test>(Test* x); // 等価。
};
int main() {
boost::checked_delete(new Test);
}
こちらはOK。結局、結論は「例8-2」を使おう!ということになります。
Notes
1. Exceptional C++ Style - 8 テンプレートをfriendに
![]() | Exceptional C++ Style―40のクイズ形式によるプログラム問題と解法=スタイル編 浜田 光之、ハーブ サッター 他 (2006/09) ピアソンエデュケーション この商品の詳細を見る |
スマートポインタ
Problem
安全にポインタを扱うためにはどうすれば良いでしょうか?
Solution
スマートポインタを使いましょう。
スマートポインタとは、ポインタのように「オブジェクトを指し示す」もので、ポインタにない機能を持つ賢いポインタのことです。
ポインタは便利な機能ですが、オブジェクトの生存期間の管理がプログラマに委ねられるため、Memory leakやWild pointer(Dangling pointer)に注意する必要があります。そこでスマートポインタを用いることで、安全にポインタを使用することができます。
C++98/C++03にはstd::auto_ptrというスマートポインタが用意されていますが、C++0xにはstd::tr1::shared_ptrという参照カウント型のスマートポインタが追加されます。
GCC 4.1.1には既にtr1が実装されているので試してみました。
安全にポインタを扱うためにはどうすれば良いでしょうか?
Solution
スマートポインタを使いましょう。
スマートポインタとは、ポインタのように「オブジェクトを指し示す」もので、ポインタにない機能を持つ賢いポインタのことです。
ポインタは便利な機能ですが、オブジェクトの生存期間の管理がプログラマに委ねられるため、Memory leakやWild pointer(Dangling pointer)に注意する必要があります。そこでスマートポインタを用いることで、安全にポインタを使用することができます。
C++98/C++03にはstd::auto_ptrというスマートポインタが用意されていますが、C++0xにはstd::tr1::shared_ptrという参照カウント型のスマートポインタが追加されます。
GCC 4.1.1には既にtr1が実装されているので試してみました。
#include <iostream>
#include <tr1/memory>
class ValueType {
public:
ValueType() { std::cout << " ValueType::ValueType()" << std::endl; }
~ValueType() { std::cout << " ValueType::~ValueType()" << std::endl; }
};
void func()
{
std::cout << " func() begin" << std::endl;
std::tr1::shared_ptr<ValueType> ptr(new ValueType);
std::cout << " func() end" << std::endl;
}
int main()
{
std::cout << "main() begin" << std::endl;
func();
std::cout << "main() end" << std::endl;
return 0;
}
実行結果は以下のようになります。
main() begin
func() begin
ValueType::ValueType()
func() end
ValueType::~ValueType()
main() end



