コーディングスタイルメモと合わせて C++ を記述する際の指針も書いていく.
C++ の利用理由
まず, _C++ _を使う以前に,C++ 以外の適切な言語がないか検討する.C++ はややこしいことで有名であるため.後に出た言語の方が良く設計されている場合が多い.本当に C++ を使う必要があるのか? Java,Python,ruby,VB.Net,C# 他の選択肢は? 速度重視のときは C++か?
方針
- メンテナンスができること
- 自分1人だけなら好き勝手書いていいけど,他の人が居る場合はみんなに分かるコードで.でも,プロジェクトを通してスキルアップができるといいね!
- エラーはできるだけコンパイル時に出るようにすること
- エラー系に関しては,実行時エラーではなくコンパイル時にエラーが発生するようにするべきである.その方が発見しやすく,原因も明確でかつ,コード修正も容易である.
- 速度より可読性を
- 基本的に可読性の良いコードは早いらしいよ
C++ 使う上でメンバに要求したいこと
- 絶対理解して欲しいこと:理解してもらなわないと困る,C++ 使う意味が無い
- クラス:理解しなかったら C++ を使う意味が無い
- 委譲:クラスの中にクラス変数を宣言して宣言したクラスに処理を任せること,継承より委譲を検討する
- 継承:クラスの大きな機能,上位クラスの機能をそのままに下位クラスで機能を拡張する.別に委譲でいいのではと思えば委譲で実現する.
- 名前空間:グローバル定数をなくす,関数,クラスの衝突をなくす
- 参照:ポインタのように使えてポインタ以上に使いやすい
- C++ のキャスト(static_cast):C のキャストはダメ,C++ ではキャストの種類が明確になっており,特に上位クラスから下位クラスに下る dynamic_cast があるため,C風キャストではそれらどのキャストが使われたか非常に不明確なため.
- クラス:理解しなかったら C++ を使う意味が無い
- そうでもないもの,100% 理解しなくていいけどある程度知っておいてよね.
- テンプレート
- 演算子のオーバーロード
- dynamic_cast
- 無理して理解しなくていいこと
- 多重継承
- テンプレートメタ
コンパイラの設定
**全ての警告を ON にして,エラーとする **.警告は,潜在的なエラーとなり得る.そのため,全ての警告について正しく対処しなければならない.Visual Studio なら /W4 /WX
C++11
C++11 を使えるなら当然使うべきである.スマートポインタ,nullptr,constexpr,static_assert が利用できるのは大きいと思う.
- 積極的に使いたい新機能 (ある程度機能が単純そう)
- nullptr:どこも指し示さない null が明確になる
- constexpr:const よりコンパイル時定数であることを強調できる
- スマートポインタ(shared_ptr, unique_ptr):ポインタの管理が楽になる
- static_assert:うまく使えばコンパイル時に定義エラーなどの誤りを検出できる
- enum class:暗黙変換が無くなるため.
- 考えて使う必要があると思われる機能 (理解してないから)
- ラムダ式:果たしてチーム全員が正しく理解できるか.逆にややこしくならないか.使用するにしても限定的にする必要があるのではないか?
- move:右辺値参照は結構ややこしいと思う.すんなり理解できなかった.
- よくわかんない(理解している気になってるだけ)
- auto:イテレーターの時は使いたい,でも,使いすぎると意味不明になりそう
- decltype:変数の型を指定できる.テンプレートの返り値の型推論?
ライブラリ
積極的に利用するべきだが,学習コストが高いライブラリは控える必要がある.特に boost はテンプレートメタプログラミングを多用しており学習コストが高くなりがちになる.取捨選択が大事になってくるチームのスキルに合わせて考える(google が使用に制限を与えているというのは大きいのではないか?).C++11 では,標準ライブラリが充実するため boost 依存がなくなるのでどんどん減っていくと予想.
- メンテナンスが継続的に行われているか
- ドキュメントが充実しているか
- 世間的にメジャーであるか(例えば,分野で検索して選択して多数上がるのか)
- 分かりやすいか
そもそも,難しくて高度なライブラリを利用するぐらいなら,他の言語を利用した方がいいのでは?
少なくともスマートポインタは,積極的に利用しても良い.スマートポインタは,new,delete などのメモリ管理の煩わしさから解放してくれる.C++11 以降では,標準ライブラリに組み込まれており,#include <memory>
から利用できる.
参照
C++ から参照型が導入された.変数の扱うようにできるが実際はポインタというもの.ポインタをあまり意識することなくポインタを扱えるので積極的に利用したい.
int val = 10;
int &ref = val; // 参照,または,エイリアス(別名) の宣言,val の 別名と捉えると分かりやすい.val の値を ref でも取り出したり代入できたりする.
int &ref2; // NG.参照は,必ず元となる変数を指定する必要がある.
ref = 100; // ref は val と等価なため val にも影響する.
std::cout << "val(value = " << val << ", " << "address = " << &val << std::endl;
std::cout << "ref(value = " << ref << ", " << "address = " << &ref << std::endl;
例えば,以下の結果となり2つの変数が全く同じポインタを指していることが分かる.
val(value = 100, address = 00F9FA84)
ref(value = 100, address = 00F9FA84)
const 参照
参照の利点としてあげられるのは,メソッドの引数に用いるconst 参照である.const 参照は,入力であることを明確にし,ポインタなみのコピーコストで,さらに NULL の心配が少ない(Effective C++).
void print(const std::string &str)
{
str = "abc"; // NG. const なのでコンパイル時エラーがでる.
std::cout << str << std::endl;
}
スマートポインタの利用
ポインタは,基本的にスマートポインタでラップする.スマートポインタは,new, delete のメモリ管理の煩わしさを無くしてくれる便利なポインタのラッパクラスである.
項目 | boost | C++ |
---|---|---|
ヘッダ | #include <boost/smart_ptr.hpp> |
#include <memory> |
所有権が単一 | boost::shared_ptr | std::unique_ptr |
所有権を共有 | boost::shared_pt | std::shared_ptr |
shared_ptr, unique_ptr は,所有者が明確でできる限りこれらを使用すること.コピーされることなくスコープから抜けると確実にメモリを解放してくれる.
int main()
{
{
std::unique_ptr<int> p = std::make_unique<int>(100);
std::unique_ptr<int> p2 = p; // NG. 所有権は共有できない
} // ここで,p は解放される
std::shared_ptr<int> p = std::make_shared<int>(100);
{
std::shared_ptr<int> p2 = p; // OK. shared_ptr は共有を許す
} // この時点で p2 は解放されるが,p が解放されないためポインタは残る
std::cout << *p << std::endl;
}
shared_ptr を C++08 では,boost,C++11 では std に切り替える
shared_ptr を C++08 では,boost::shared_ptr,C++11 では,std::shared_ptr としたいときは多いと思う.そのときは,自身の namespace で using *::shared_ptr とすればよい.
#if __cplusplus >= 201103L
namespace my_project {
#include <memory>
using std::shared_ptr;
using std::make_shared;
using std::static_pointer_cast;
using std::dynamic_pointer_cast;
using std::const_pointer_cast;
} // namespace my_project
#else
namespace my_project {
#include <boost/shared_ptr.hpp>
using boost::shared_ptr;
using boost::make_shared;
using boost::static_pointer_cast;
using boost::dynamic_pointer_cast;
using boost::const_pointer_cast;
} // namespace my_project
#endif
int main()
{
using namespace my_project;
shared_ptr<int> p = make_shared<int>(100);
return 0;
}
関数
1行関数を恐れない
return のみの関数を恐れない.意味が明確になるのであれば,return のみの関数でも実装する.ただし,冗長的なのは禁止.このさじ加減は結構難しい.
/*
* おそらくこれは冗長的
*
* 普通に str + suffix と書いた方が伝わるか?
*/
std::string appedSuffix(const std::string &str, const std::string &suffix)
{
return str + suffix;
}
/*
* 画像保存の際 photo_<name>.png とフォーマットが決まっているとき
* フォーマットに従った名前が欲しいときには有効か?
*/
const std::string PREFIX = "photo_";
const std::string EXT_NAME = ".png";
std::string createImageFilename(const std::string &name)
{
return PREFIX + name + EXT_NAME;
}
テンプレート
C++ において最も注意深く利用しなければならない機能と思う.はまればテンプレートメタプログラミングになり,誰も理解できないコードが増えることに・・・.一方,いわゆるジェネリックとして利用すればオーバーロードによる複数の型定義が必要なくなる.
// まだ,このような型で共通の操作を行うようなテンプレートの使い方なら良いと思う.
template <typename T>
const T & addTwo(const T &value)
{
return value + static_cast<T>(2);
}
// また,ジェネリック型のようにいろいろな型を保存するコンテナ的な扱いでも良いと思う
template <typename T>
class Point2D
{
T x;
T y;
};
テンプレートメタプログラミング
正しく理解するのに時間を要するので禁止.メンテナンスできる?<, >
の荒らしと格闘できるか?
クラス
基本的に コピーコンストラクタ,代入演算子は禁止する.
// C++08 のとき
class A
{
public:
A();
~A();
private:
A(const A &rhs);
A& operator=(const A &rhs);
};
// C++11 のとき
class A
{
public:
A() = default;
~A() = default;
private:
// 特に move は明示的に禁止にしないと痛い目見そう
A(A const&) = delete;
A(A&&) = delete;
A& operator =(A const&) = delete;
A& operator =(A&&) = delete;
}