この記事について
C++未経験者が一年間かけてC++を勉強した軌跡と学んだことを紹介します。
C++は参考書に書いていない様々な落とし穴があります。初心者の方はこの記事を参考にすることで、知らずにバグを埋め込んでしまうことを防ぎましょう。
C++を勉強する前提
元々のプログラミング歴
筆者はC++を勉強する前は次のような少しのプログラミング経験がありました。C++を勉強することによりプログラミングの基礎が身につき今ではRuby,Pythonなどモダンな言語もすぐに身に着けることができました。
- 大学の教養の授業で教養にてCでハローワールドと入出力を勉強
- 1ヶ月のJavaプログラミング研修
- C#開発半年
本記事で参考とする規格
初心者C++erを対象とした記事ですのでC++の規格(バージョン)を解説します。C++の規格は下記の様に複数あります。
- C++98(C++03)
- C++11
- C++14
- C++17
- C++20(2020年にリリース予定)
本記事では私が当時学習したC++03を前提に記述します。
ただしC++03とC++11では大きなアップデートがあり、C++11以降は煩雑な内容を簡単に書けたりしますので特に制限のない方はC++11以降を学習すると良いでしょう。
C++について
C++はC言語の拡張として1983年に登場したそうです。
C++は様々なパラダイムに対応したプログラミング言語で構造化プログラミングもできますし、オブジェクト指向プログラミングもできますし、ジェネリックプログラミングもできます。それぞれC言語から引継いだもの、例外、クラス、テンプレートなどによって実現しています。これらは規格書でも明確にそれぞれ章として独立して説明されています。
C++を使いこなすならばこれらのうちどの分野を使えるのかを明確にしておいた方がいいです。例えば構造化プログラミングとテンプレートを駆使したプログラミングは同じ言語で記述されているとは思えないような差異があります。
C++を勉強し始めた人は下記のレベルがあると思っています。
- Cの文法がわかる
- C++で拡張された文法がわかる
- クラスを利用できる。
- クラスを用いて適切なオブジェクト指向プログラミングができる
- STLを利用できる
- テンプレート機能を利用できる。
- テンプレート機能を規格の限界まで駆使して利用できる。
- 規格書を参照する
- 規格書に提案が行える。
私はオブジェクト指向とSTLを活用する程度の初心者です。テンプレートに関して本記事では深く言及しません(できません)。
読んだ本
本章では学習にあたり利用した書籍を紹介します。
C++参考書
猫でもわかるC++第2版、やさしいC++第4版
C++の参考書であり特に印象にないです。参考書はなんでもいいのでは。
これらの本に書いていないイディオムや注意点はこの後説明します。
C++ポケットリファレンス
C++の書き方で悩んだときにサッと取り出せる便利な書籍です。C++11の内容はC++11と記載されているためわかりやすいです。
オブジェクト指向/デザインパターン
C++と並行でオブジェクト指向/デザインパターンについても勉強しました。C++はオブジェクト指向プログラミングとは切り離せないと思ったので学習しました。最初はデザインパターンという用語もわからないレベルでした。デザインパターンとは先人たちの設計のエッセンスを抽出した汎用的なパターン集です。デザインパターンはオブジェクト指向設計により多くのパターンが導出されています。
デザインパターンを学ぶことで以下のメリットがありました。
- オブジェクト指向設計のメリットを理解できる。
- オブジェクト指向設計自体の理解につながる。
- テスト可能なコードの書き方がわかるようになる。(ex.リファクタリング、依存性を排除する)
オブジェクト指向開発講座
おすすめ!!
オブジェクト指向の何たるかを当時理解していませんでしたが、オブジェクト指向のメリット、(設計における)静的分析・動的分析、実例を流れるように説明してくれる1冊でした。初心者の方はまずこれを読むといいと思います。UMLの勉強にもなります。
本書で理解したオブジェクト指向のメリットとは設計時にUMLで記述したモデル、ある程度形を保ったまま実装できるというものです。設計時の話になりますが静的分析とはあるモデルの持つ機能を列挙したもので、UMLとしてはクラス図になります。動的分析とはモデル通しの相互のやり取りを表現したもので、UMLとしてはシーケンス図、アクティビティ図などになります。本書ではこれらのモデリング方法とC++での実装方法を順番に説明してくれます。
オブジェクト指向のこころ
オブジェクト指向を多少理解した後に読み、本書で初めてデザインパターンという概念を学習しました。「プログラムは時がたつともに要求が変化し対応が必要であるが、人間には未来予知は不可能であるので完璧なプログラムは作ることができない。ではどのように変化に対応するか、それは変化が起こりそうな部分を封じ込め、他のソースコードから隔離することである。」ということを学びました。
本書で紹介されているデザインパターンの一部を下記に記載します。
パターン | 説明 |
---|---|
Facade | 利用するのに様々な前提条件のいるAPIの、前処理をして新しいシンプルなAPIを提供する |
Strategy | オブジェクトの持つ責務を直接実装せず、責務自体をクラスとすることで使い分けることを可能にする。 |
Bridge | インターフェースと実装を分離する。PravateImpleイディオムとかはこれ |
Observer | あるオブジェクトの変更を、別のオブジェクトに通知する |
TemplateMethod | 前処理、処理、後処理などの一連の流れを基底クラスで表現し、処理の具体的な実装を派生クラスで実装する。 |
デザインパターン入門/デザインパターン入門 マルチスレッド編
デザインパターンのリファレンスです。頭から読むというよりわからないパターンが出てきたときに該当部分を読むという使い方をしました。
C++お作法が書いてある本
いよいよC++の話です。C++は参考書に書いてある内容だけでは知識が不足しており、下記の書籍で紹介される”お作法”を身に着けたうえで実装を行わないと容易にバグを埋め込んでしまう言語なのです。以下の書籍で紹介されている内容の一部を本記事の後半で説明しますのでそれぞれの本の項では書籍の紹介程度にとどめます。
EffectiveC++/MoreEffectiveC++
C++の参考書を読んだあとすぐに挑戦しましたが挫折しました。ただし記載されている内容はC++を利用する上で必須なものも多いです。
挫折する理由として内容が高度ということもありますが、日本語訳がひどいという原因の方が大きいです。
下記で紹介する「C++ Coding Standards」の方が日本語、解説ともにわかりやすいです。
C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス (C++ in‐depth series)
お作法を101のルールに分けて紹介しており、ルールは「コンストラクタ」、「例外」、「継承」で気を付けることのように分かれているため実装時に参照するのもわかりやすいです。
C++ スタイルブック (IT Architects’ Archive―CLASSIC MODERN COMPUTING)
上記のC++ Coding Standardsを更に簡素かしたものです。足りない部分もあると思いますが、一番最初はこのレベルから始めてもいいかもしれません。非常に薄いのでそこもおすすめです。
STL標準講座
C++を簡潔に、バグなく記述するためにはSTLの利用は不可欠です。STLはStandard Template Libraryの略でテンプレート機能を使って実装された便利なライブラリです。STLは大きく分けると以下で構成されておりそれぞれ学習が必要です。
- コンテナ
- イテレータ
- アルゴリズム
- (String)
コンテナは配列を抽象化したもので、任意の型の集合とその操作を提供するクラスです。std::vectorなどがあります。
イテレータは集合の要素を抽象化したもので、コンテナの先頭や、終端、forループなどを実現します。
アルゴリズムは集合に対する操作、具体的には各要素への関数適用、要素検索、要素削除など自力でも実装できるが、あると便利なものを提供します。アルゴリズムで提供されているものは自作せずにそのまま利用しましょう。std::transform,std::for_each,std::find_ifなどがあります。
以下学んだこと
以上、本の紹介をしてきましたが、それらに共通で記載されていることなどを本章に列挙します。
下記で全てではないですが、下記の内容が最低限わかっていないと、C++をちょっとできる人からツッコミが入ります。
クラス設計とC++の作法
単一引数のコンストラクタにはexplicitを付ける
単一引数は意図しない型変換が起こります。explictを付けると防げます。
class X{
public:
explicit X(val):val_(val){}
private:
int val_;
};
virtualデストラクタ
基底クラスのデストラクタはvirtualでなければいけません。裏を返すとvirtualデストラクタを持たないクラスから継承してはいけません。
基底クラスのポインタ型の派生クラスのポインタをdeleteしたときにバグります。
class Base{
public:
// ...
virtual ~Base() // OK
// ~Base() // NG
};
class Sub : public Base{
public:
// ...
virtual ~Sub()
};
constメンバ関数
メンバ変数を変更しないメンバ関数はおしりにconstを付けてください。
class X{
public:
// ...
int val() const {return val_;}
private:
int val_;
};
const参照渡し
クラスを関数に渡すときは(const)参照渡しを使用します。クラスを内部で変更するときはconstはなくていいです。
文字列を表すstd::stringなどもこれに該当します。文字列はコピーで渡さないようにしてください。
クラスを値型で渡すとオブジェクトが意図せずコピーされ下記の弊害があります。
- 性能上の問題
- ポリモーフィックに動作することを望んでいたら、そのように動作しない
- 派生型のオブジェクトのスライスが起こる
void func1(MyClass c); // NG
void func1(const MyClass& c); // OK
例外は値で投げて参照で補足
ポインタで投げると、catch側がdeleteしなきゃいけなくなります。
値で補足するとコピーが走ってしまいます。
void func()
{
try{
throw std::runtime_error("error");
}
catch(const std::runtime_error& e)
{
std::cout << e.what();
}
}
初期化子リストを使って初期化を行う
コンストラクタの処理内部で初期化しようとしているそこのあなた。あなたがしていることはコンパイラが暗黙的に初期化したメンバに対する代入であり初期化ではありませんよ。
class X{
public:
explicit X(val):val_(val){}
private:
int val_;
};
実装技法
STL(コンテナ・イテレータ)
自分で配列を作るのはやめて、デフォルトでstd::vectorを使いましょう。
STL(アルゴリズム)
自分でforループを作るのはやめて、STLで提供されたアルゴリズムを使いましょう。
アルゴリズムを使うことでバグが減り、意図が明確になります。
サンプルコードは次項にまとめます。
アルゴリズムの述語は関数オブジェクトを利用する
アルゴリズムは具体的にはstd::findの行です。述語とは第三引数のことです。ここには関数の形の何か、例えば関数ポインタ、関数オブジェクト、(C++11ではラムダ式)が入ります。C++03では関数オブジェクトを使うと関数ポインタより速いらしいです。
find_ifはif分の条件を外だしでき、関数の複雑度を低減させることができるので理解必至です。
template <int N>
struct is_same{
bool operator()(int i){return i==N;}
};
int main() {
std::vector<int> vec;
for(int i = 0;i<10;++i){ vec.push_back(i);}
// 2を探す。
std::vector<int>::iterator vec_itr;
vec_itr = std::find_if(vec.begin(),vec.end(),is_same<2>());
std::cout << *vec_itr;
}
イディオム
RAII
RAIIはC++を利用する上で必須のテクニックです。RAIIはResource Acquisition Is Initializationの略で、直訳すると「リソースの確保=初期化」ですが意味が分かりませんね。私なりに解釈するとスコープを抜けたらリソースが自動開放されるようにするです。
C++のデメリットとしてGCがないので自分でリソースの解放を行わないとメモリリークが発生することがあります。しかしデメリットは裏を返すとメリットにもなり得ます。C++にはスコープという概念があり、スコープとは変数の生存期間です。ある関数内で宣言された変数はスタックメモリに確保され、関数から抜けると自動的に解放されます。これを逆手に取ったテクニックがRAIIです。コンストラクタでリソースを受け取り、デストラクタでリソースを解放するクラスを用意します。そうこれはスマートポインタのことです。スマートポインタはコンストラクタでポインタを受け取り、デストラクタで保持するポインタをdeleteするのです。
int func1 {
std::auto_ptr p(new int(1)); // 注:std::auto_ptr はC++11以降は非推奨です。
}// 関数を抜ける時に自動的にdeleteされる。
これはスマートポインタに限らず自分でコンストラクタで行いたい処理とデストラクタで行いたい処理を書いたクラスを用意することで、例えば、ファイルの閉じ忘れなどにも応用できるので便利です。C++11のunique_ptrでは引数で自動開放時に呼びたい処理を関数オブジェクトやラムダ式で指定することが可能です。
下記の記事で丁寧に紹介されていました。
https://qiita.com/wx257osn2/items/e2e3bcbfdd8bd02872aa
その他
boostで勉強することについて
C++を勉強するためには有用なライブラリのソースを読むことはとても効果があります。例えばboostです。boostはC++の標準化委員会のメンバが所属するコミュニティでC++の便利なライブラリを公開しています。またboostの成果は新しい規格のC++に採用されることも多くあります。C++03しか使えない状況でもC++11の機能をboostを参考に自作してしまうということもできてしまいます。
boostのソースコード入手方法
下記のサイトからダウンロードできます。
http://www.boost.org/users/download/
規格書リーディングについて
C++はその難解で巨大な言語規格であるがゆえに、完全に規格に準拠していないコンパイラ(VC++)も巷に転がっており、ネット上の意見をそのまま鵜呑みにすると痛い目を見ることがあります。気になったことは一時情報である規格書を読むことで誤解がなくなります。規格書は比較的簡単な英語で書かれているため全部目を通すことは難しくても、関連する箇所だけでも読んでみると面白いとおもいます。
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
###コーディング規約
本記事で紹介してきた内容だけでもすごく多く、初学者の人は勉強に苦労されることでしょう。
下記に比較的まとまっており、勉強に役立つサイトを紹介して本記事を終わります。長々とお読みいただきありがとうございました。
GoogleCoding規約
https://ttsuki.github.io/styleguide/cppguide.ja.html
サイボウズ規約
https://cybozu.atlassian.net/wiki/spaces/pubjp/pages/8159240