Help us understand the problem. What is going on with this article?

JavaはやったことあるCプログラマによるC++ 勉強メモ - 基礎編 -

More than 1 year has passed since last update.

Javaを学校で習い、Cを使う僕がC++について勉強したことをメモするページ。コードは文法や規則を説明するだけであり、セキュリティや保守性などは何も考えてないので参考にしてはならない。

なお親クラス/子クラス と書いた箇所があるが、それぞれ基底クラス/派生クラスを意味する。

参照

C++ では参照渡しが使える。関数宣言の際に&をつけると使える。
呼び出す時は値渡しと同じ形で呼ぶ。

void Func( int a, int &b){}

int a = 0;
int b = 1;

Func(a,b); // a は値渡し、b は参照渡し

//const 参照のときは変数でなく値を直接渡せる。
//値が渡されたときはテンポラルオブジェクトを作成し、それを参照する。
void f1( const int &a ){}
f1(10); 

関数の引数以外でも使える。

int n[10];
int &r = n[3];

//const 参照にはテンポラルオブジェクトを渡せるが、その寿命は生成されたその文の終わりまでなので注意
const int& Hoge(const int &a) { return a; }

const int &ref = Hoge(3);
cout << ref << endl; //注意

型情報

  • typeid()で実行時型情報を取得できる。このとき参照やconstは無視して判断される。情報の型は const type_info&で、==!=で比較できる。

メモリ確保

malloc()/free() に代わりnew/delete がある。後者ではコンストラクタ/デストラクタが呼ばれる。
newはメモリ確保に失敗したとき、bad_alloc 例外を投げるが、new(nothrow) とすれば代わりにNULL が返る。
さらにdelete NULL としたときは、何もせずに終わるので事前のNULL 検査が不要。

関数のデフォルト引数

関数の引数にはデフォルト引数が設定可能。

/*
 * 途中の引数にはデフォルトを指定できない。
 * 関数プロトタイプ宣言をする時はそちらで指定する。
 * 指定できるデータは静的なデータのみ。ただしfunc(1)のように静的なデータ渡した関数の返り値もいいらしい(状態変数で出力が変わりそうだけど)。
 */
void StrCopy(char *dst, const char *src, int first = 0, int length = -1);

呼び出す時はすべてのデフォルト値を持つ引数に値を渡すか、もしくはそれらすべてに値を渡さないのどちらかのみ。そうしなければビルドエラー。Apple LLVM コンパイラ7.3.0 でビルドしたら通った。g++ 4.8.5 でも通った。

関数のオーバーロードがある時はコンパイルエラーになることがある。
下記のコードでは上の方のFunc()を呼び出すことはできない。

void Func(int a, int b){}
void Func(int a, int b=0, int c=0){}

void main(void){
    //どっちを呼んでいるのか区別できない
    Func(1,1);
}

クラス

  • クラスのインスタンスが引数に渡されるときは仮引数を初期化するためにコピーコンストラクタが呼ばれるので、これを定義しておこう。デフォルトはメンバの値のコピー。
  • 同じ型の代入演算のオーバーロードはしておこう。デフォルトはメンバの値のコピー。代入演算以外はデフォルトはない。
  • staticメンバ変数を使用するときはクラスの宣言の中とは別に、int Integer::m_valueのように定義する必要がある。ただし静的整数型は宣言の中で初期化できる(定義は別に必要)。

アクセス指定子

  • public
  • protected
  • private : 省略可能

コンストラクタ/デストラクタ

クラスの変数宣言の際に引数なしコンストラクタのために引数の直後に括弧をつけると、関数プロトタイプとみなされてしまうので注意

//NG
Integer a(); 

//OK
Integer a;
Integer a = Integer();

コンストラクタは暗黙のキャストでも使われる。これを抑制したければexplicitをコンストラクタにつける。予期せぬエラーにつながるので、コピーコンストラクタ以外はexplicitをつけておくといい。

class MyDouble{
private:
    double m_d;
public:
    MyDouble(double d){ m_d = d; }
    double getValue(void) const {return m_d;}
};

void show(const MyDouble& d){
    cout << d.getValue() << endl;
}

int main(void){
    show(1.1); //ここでコンストラクタを用いた暗黙のキャスト
}

デストラクタが呼ばれないケース

  • コンストラクタ内でmalloc()/new をして例外が発生すると、デストラクタが呼ばれずにオブジェクトが捨てられ、メモリが解放されないため注意!!!
  • exit()でプログラムが終了した際も、静的変数以外はデストラクタが呼ばれない。デストラクタに終了時のファイルへの書き込みなどを期待してコードを書くと痛い目を見るかも。あくまでデストラクタはオブジェクトを適切に破棄する用途だけに使おう。

派生クラスにおけるコンストラクタ/デストラクタ

コンストラクタは基底クラスから順に呼ばれる。デストラクタは派生クラスから順に処理される。
このためコンストラクタ/デストラクタ内で純粋仮装関数を使用すると、派生クラスの関数でなくコンストラクタ/デストラクタを処理中のクラスの関数を呼び出そうとしてエラーになる。

基底クラスの引数付きコンストラクタは次のように呼ぶ。なお、基底クラスのメンバも同様の記述で初期化できる。

Child.h
class Child : public Parent {
    protected:
        int m_n;
    public:
        Child(int n);
}
Child.cpp
#include "Child.h"

Child::Child(int n) :
    Parent(n) ,
    m_n(2) //基底クラスの m_n メンバを2で初期化
    {}

delete でオブジェクトを解放する時、その型が基底クラスのときは基底クラスのデストラクタが呼ばれ、派生クラスのデストラクタが呼ばれない。これを解決するためには、基底クラスのデストラクタを仮想関数にすればよい。とりあえずデストラクタにはすべてvirtualをつけて修飾しよう。

演算子オーバーロード

演算子の前にoperatorをつけて関数名とし、引数は参照型にする。

MyClass.h
class MyClass {
public
   void operator=(const MyClass& other);
}
MyClass.cpp
void MyClass::operator=(const MyClass& other){
    ...
}

ただし上の場合は該当クラスのオブジェクトが右オペランドになる場合のみ有効。左オペランドになるときはクラスの宣言の外でオーバーロードする。このときそれはフレンド関数にすれば便利。

class MyDouble{
private:
    double m_d;
public:
    MyDouble(double d) { m_d = d; }
    friend MyDouble operator*(double, const MyDouble&); //フレンド関数はメンバ関数でない
}

MyDouble operator*(double d, const MyDouble& md){
    return MyDouble(d + md.m_d);
}

メンバ関数ポインタ

int (CLASS::*func)(int a) = &CLASS::abs; //代入には&が必要

CLASS c;
(c.*func)(1); //c.*func(1); では優先度の問題でNG。クラス内では this->* は省略できない

メンバ関数ポインタは仮想関数でも適切に処理する。メンバ関数の呼び出しは処理系依存で、メンバ関数ポインタのサイズはわからない。

SBase s //SBase はBase を継承。Base::Hoge()をオーバーライド
CallHoge(s);

void CallHoge(Base & b){
    void (Base::*mfp)() = &Base::Hoge;
    (b.*mfp)(); //上の呼び出しではBase でなくSBase のHoge()が呼ばれる。
}

メンバ変数ポインタ

int Point::*mp = &Point::y;

継承

仮装関数

親クラスで定義されたメソッドmethod()が子クラスでオーバーライドされており、子クラスのインスタンスが親クラスの変数classに格納されているとき、class.method()を呼ぶと実行されるメソッドは親クラスのメソッドとなる。以下の例では出力はParentになる。

#include <iostream>

class ParentClass{
public:
    void func(void){ std::cout << "Parent\n"; }
};

class ChildClass : public ParentClass{
public:
    void func(void){ std::cout << "Child\n"; }
};

int main(void){
    ChildClass cc;
    ParentClass& pc = cc; //キャストアップ

    pc.func();
}

この状況で子クラスのメソッドが呼ばれるようにしたければ、親クラスのメソッド宣言にvirtual修飾子をつける。その関数は仮想関数と呼ばれる。子クラスの方にはつけなくてもいい。

class ParentClass{
public:
    virtual void func(void){ std::cout << "Parent\n"; }
};

一方で基底クラスの関数をわざわざ呼びたいときがある。そのときは関数名の前にParentClass::をつけて呼べばよい。

int main(void){
    ChildClass cc;
    ParentClass pc = cc;

    //func はParentClass の非純粋な仮想関数で、ChildClassでオーバーライドされている
    pc.func();              //ChildClass のfunc()
    pc.ParentClass::func(); //ChildClass のfunc()
}

仮装関数の宣言のみし、定義はしたくないときがある。これは仮装関数の宣言に 0 を代入することで可能。この関数を純粋仮装関数と呼ぶようになり、それをもつクラスを抽象クラスと呼ぶ。抽象クラスはインスタンスを作ることができない。

ParentClass.h
class ParentClass{
public:
    virtual void func(void) = 0;
};

親クラスのコンストラクタ呼び出し

"コンストラクタ/デストラクタ"の節を参照。

多重継承

クラスは複数のクラスを継承できる。同じクラスを二つ分直接継承することはできず、間に派生クラスを挟む必要がある。このとき同じクラスのインスタンスが二つ別々に作られる。インスタンスを一つにしたければ仮想継承を用いる。
multiple-inheritance.png

上の例ではMyHogeのインスタンスを直接Hogeにキャストアップできない。なぜならAHogeの方か、BHogeの方か、どちらのHogeにキャストアップすべきかわからないからである。このときは一度AHogeにキャストアップしてからHogeにキャストアップする。

MyHogeの関数がAHoge BHoge の両方でオーバーライドされているとき、さらにそれをMyHogeでオーバーライドしたら、AHoge BHogeの両方の関数がオーバーライドされる。

仮想継承

多重継承で同じクラスのインスタンスを二つ作らないためには仮想継承をする。
仮想継承されたクラスのコンストラクタは最も遠い派生クラスからしか実行されない。
仮想関数の実装が二つあるとコンパイルエラーになるので、最派生クラスでは仮想関数を定義する必要があることも。

class Hoge{
private:
    Hoge(void){}
public:
    Hoge(int i){ cout << i << endl; }
    virtual void show(void) = 0;
};

class AHoge:
    virtual public Hoge{
public:
    AHoge(void): Hoge(1){}
    virtual void show(void){ cout << 1 << endl; } 
};
class BHoge:
    virtual public Hoge{
public:
    AHoge(void): Hoge(2){} //BHogeのインスタンスではHoge(2)が呼ばれるが、MyHogeのインスタンスではHoge(2)は呼ばれない
    virtual void show(void){ cout << 2 << endl; } 
};

class MyHoge:
    public AHoge,
    public BHoge{
public:
    AHoge(void): Hoge(3){} //Hoge(3)がなければHogeのコンストラクタが呼ばれないため、MyHoge がコンパイルできない
    virtual void show(void){ cout << 3 << endl; } //これがなければAHogeとBHogeそれぞれのshow()が実装としてあるため、コンパイラはどちらの関数を呼び出すか判別できずエラーになる。
};

仮想デストラクタ以外には純粋仮想関数のみをメンバに持つクラスをインターフェースクラスという。これは上のような面倒な気をもむことなく多重継承を実現できる。

フレンド

あるクラスに対するフレンド関数やフレンドクラスは、メンバでないのにプライベートメンバにアクセスできる。複数のクラス間で共有もできる。

名前空間

namespace により名前空間を設定できる。特に指定がなければシンボルはグローバル名前空間に所属する。

#include <iostream>

namespace My{
    namespace A{
         void func(void){ std::cout << "in My::A\n"; };
    }
    namespace B{
         void func(void){ std::cout << "in My::B\n"; };
    }
    void func(void){ std::cout << "in My\n"; };
}
void func(void){ std::cout << "in Global\n"; };

int main(void){
    func();        //in Global
    ::func();      //in Global
    My::func();    //in My
    My::A::func(); //in My::A
    My::B::func(); //in My::B
}

名前空間内でそこに属する関数などを使う時は名前空間::は不要であり、もし名前がグローバル名前空間と衝突すれば自空間内にあるオブジェクトを優先する。

名前空間内でも関数プロトタイプと関数定義を別にできる。ブロックが別れていても上から見ていくようだ。ブロックを逆にしたらコンパイルエラーになった。

namespace My{
    void fA(void);
    void fB(void);
}
...
namespace My{
    void fA(void){ fB(); }
    void fB(void){}
}

using

  • using namespace 名前空間;名前空間::を省略できる.
  • using 名前空間::識別子; で特定の識別子についてのみ名前空間::を省略できる
  • namespace 別名=名前空間名; で名前空間に別名をつけられる

using namespace を推奨しない人もいる。例えば、以下のコードを書いたときはName2Hoge()を含まなくとも、後でそれが追加されたときに、それまでのすべてのHoge()Name1::Hoge()に変更する必要がある。このように保守が困難になるので、これらの省略はcoutendlなど一般的なものに限定した方がいいだろう。
ヘッダファイル内では書かない方が無難。

using namespace Name1;
using namespace Name2;

Hoge(); //Name1::Hoge()

なおusing は関数内でも使え、そのときはスコープが限定される。このような使い方なら許容しやすいだろう。

usingはクラスの継承においても有効な使い道がある。基底クラスが同名メンバ関数を複数持つ場合、派生関数でその関数のうち一つをオーバーライドすると基底クラスの同名メンバ関数はすべて使えなくなる。このとき逐一基底クラスのメンバ関数の同名ラッパ関数を定義してもいいが、usingによりそれを省略できる。

class Parent{
public:
    virtual void show(int i) const { cout << i << endl; }  
    virtual void show(char* s) const { cout << s << endl; }  
};

class Child : public Parent{
public:
    virtual void show(int i) const { cout << "Child : " << i << endl; }
    //これを使えばオーバーロードしてないshow()が呼ばれたら基底クラスのshow()を呼ぶ。
    //これを private: においたら、show()をプライベートにでき、その使用を禁止できる。
    using Parent::show; 
};

int main(void){
    Child c;
    c.show("Hello"); // "Hello"
    c.show(1);       // "1" 
}

テンプレート

関数テンプレート

template <typename T1, typename T2> void show(T1 a, T2 b){
    std::cout << "a : " << a << std::endl
        << "b : " << b << std::endl;
}

//call show
show(-10,"name");
show<double, int>(-0.89,899); //型指定も可能

//この型の関数の実態を作成。
//関数の定義を外に見せず、特定の型のみを受け付けるようにしたい時に使える。
//テンプレート関数の定義はcppファイルに書き、下の文をヘッダファイルに書けば、
//<int, int>用のshow()のみが使えるようになる。
template int show<int, int>(int a, int b); 

それぞれの型に合わせた実態は、コンパイル時に作られる。リンク時より前に実態が作れられるため、関数テンプレートの定義はヘッダに書くべきでない。

関数の動作に沿わない引数が渡されたときはコンパイルに失敗する。

class C{
public:
    int m_c;
}

class D{
public:
    int m_d;
}

template <typename T>
showC(const T a){ cout << a.m_c << endl; }

int main(void){
    C c;
    D d;

    showC(C);
    showC(D); // クラスD はm_c メンバを持たないのでコンパイル失敗。
}

クラステンプレート

クラステンプレートを使うときは、関数テンプレートと違いテンプレートの型を明示的に指定する。
また、型のデフォルト引数を取れる。

template <typename T1 = int, typename T2 = T1>
class MyClass{
public:
    T1 m_t1;
    T2 m_t2;
}

int main(void){
    MyClass <int, int> c1;
    MyClass <int>      c2;
    MyClass <>         c3;
    MyClass            c4; //NG,デフォルト引数があっても<>で型の指定をする必要あり
}

クラスのメンバ関数を定義するときもテンプレートの記述を付加しなくてはならない。

template <typename T>
class MyClass{
private:
    T   v; //ここには"template <typename T>" は不要。
    int c = 0;
public:
    void setValue(T v); 
    int getCount(void);
    //以下の関数定義は省略する
    T getValue(void);
}

template <typename T> 
void MyClass<T>::setValue(T v){
    this->v = v;
    c++;
}

//getCount 自体は Tを用いないがクラス名にテンプレートが使われるので、それが必要。
//MyClass だけではクラス名にならず、MyClass<T> として初めてクラス名になるので、
//MyClass:: でなくMyClass<T>:: と書く。
template <typename T> 
T MyClass<T>::getCount(void){
    return c;
}

テンプレートクラスの特殊化

テンプレートクラスは汎用な動作を定義できるが、特殊化により特定の型に対して指定した動作を行えるようになる。

//元になるテンプレート
template <typename T>
class Hoge{
    static const T MAX;
    static const T MIN;
}

//unsigned short に向けたテンプレート。クラス全体を定義
template <>
class Hoge<unsigned short>{
    static const unsigned short MIN = 0;
    static const unsigned short MAX = USHRT_MAX;
}

//int に向けたテンプレートの、MINメンバ変数のみを定義
template <> const int Hoge<int>::MIN = INT_MIN;

また、複数のテンプレート引数を取るクラスの場合は、一方のみを指定したり、template <typename T, typename *T>というようにそれぞれの型引数の関係を制限したものも記述できる。

テンプレートのテンプレート

テンプレートクラスやテンプレート関数はテンプレートを引数にできる。ただし特殊な構文での宣言が必要。

template <template <typename, typename> class TEMPL> //テンプレート引数が2つあるテンプレートクラスをテンプレート引数とする。
//template <template <typename T, typename ALLOC = allocator<TYPE>> class TEMPL> //このようにテンプレート引数に変数を当てることもできる。デフォルト引数を指定するのに使える。
class Hoge{ ... };

Hoge<vector> hoge; //vector<int> でなくvector

定数のテンプレート

定数のテンプレートはコンパイル時にデータが決まるので、静的なデータのみで処理をしたメンバ関数の結果には定数として扱え、配列の要素数の指定などに使える。

template <int N>
class Hoge { ... };

Hoge<3> h;

例外

void func(void) throw(const char*){ //例外を返すことを宣言。
                                    //ここにない例外を返すとunexpected()がよばれ, デフォルトではterminate()がそのまま呼ばれる
    try{
        if(someCond){
           throw "some error happened"; //例外を投げる
        }
       ...
    } catch(const char* errmsg){  //型に一致した例外のみ処理
        std::cerr << errmsg << std::endl;
        throw errmsg;
    } catch(...){ //... ですべての例外を補足
                  //基本的に使うべきでない。異常終了を避けるためにmain() で補足するなどには使ってもいいか

        throw;    //catch節内ならオペランドなしで補足された例外を投げられる
    }
}

例外がプログラム内で細くされなければ terminate()が呼ばれてプログラムが終了する。

例外としてクラスが投げられたときは参照で受けるのが普通。値渡しだと例外クラスのコピーが発生するため。
例外の補足にはキャストアップにより複数のクラスをひとまとめにして使える。

コンストラクタ・デストラクタでの例外

コンストラクタで例外が発生し送出されると、対応するデストラクタは呼ばれず、初期化が終わってないメンバのデストラクタも呼ばれない。
デストラクタで例外が発生し送出されると、オブジェクトは解体されるがメモリの解放が行われない。そのためデストラクタではすべての例外を処理すべき。

キャスト

  • static_cast : cにもある静的な型変換。(int*)aなどと変わらないことが多いが、コンパイル時にエラーを検出しやすくなるためこちらを推奨
  • reinterpret_cast : 参照やポインタが関わるキャスト。int* => char*, iostream& => string&, int* => int など。バイトオーダーなど環境依存になるため危険。
  • const_cast : ポインタや参照のconstを外すキャスト。
  • dynamic_cast : キャストが正当なものかを動的に判定し、不正ならば、参照を返すときは bad_cast例外を投げ、ポインタを返すときはNULLを返す。ダウンキャストなど、動的にインスタンスの型が決まる場合に使える。
//static_cast
int i = 1;
double d = static_cast<double>(i);

//reinterpret_cast
int n = 0x12345678;
const char* p = reinterpret_cast<const char*)(&n);

//const_cast
//const int* p;とする
void Hoge(int* p);

Hoge(const_cast<int*>(p));

extern "C"

C++ の関数をCで呼べるようにする。
これがないと、関数のシンボル名がfuncでなくなり、Cから使えなくなる。

extern "C" void func(int c){
    ....
}

Ref

  1. ロベールのC++入門講座
  2. washiの備忘録

クラス・ライブラリの例

  • string : 文字列
  • stringstream : sscanf() やsprintf()の代わりに使えそう
  • fstream : ファイルのストリーム。open(), fgetc(), fputs()の代わりに使えそう。バイナリデータは<< >>でなくread write で入出力する。
  • iomapsヘッダ : ストリームへの設定をするマニピュレータを含む
  • コンテナクラス : 連想配列やリストなど、同じデータ型のデータを複数保持するためのクラス
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした