LoginSignup
23
20

More than 5 years have passed since last update.

LL系言語しか使ってこなかったゆとりプログラマがC++に手を出した時のメモ

Last updated at Posted at 2016-03-07

タイトル通り、LL系言語(Python, Perl, Luaあたり)しか使ってこなかったゆとりプログラマが、C++ に手を出そうとしたときに調べたことなどをとめどもなく書いていきます。

インスタンスの生成方法は 2 通りある

cpp
void hoge()
{
    foo::Foo f = foo::Foo();
    f.hello();
    ...
}
cpp
void hoge()
{
    foo::Foo* f = new foo::Foo();
    f->hello();
    ...
    delete f;
}

前者の方法だとインスタンスはメモリのスタック領域に格納され、関数 hoge() から抜ける際に自動的に破棄される。後者の方法ではインスタンスはヒープ領域に格納され、明示的に delete を呼ぶまで破棄されない。return 直前で delete を呼んでいても、例外等でそこまでたどり着かない場合もあるので要注意。

クラス継承の定石

cpp
namespace foo {

    class CBase {
        public:
            CBase();
            virtual void hello() = 0;
    };

    class Foo : public CBase {
        public:
            Foo();
            void hello();
    };

}

override する前提のメソッドは、基底クラス側の宣言時に接頭辞 virtual を付けて、0 を代入しておく。override し忘れるとエラー。

基本クラスのメンバへのアクセス制限

public

cpp
namespace foo {

    class CBase {
        public:
            CBase() {};

            int x = 10;

        protected:
            int y = 20;

        private:
            int z = 30;
    };

    class Foo : public CBase {
        public:
            Foo() {};

            void foo() {
                this->x;     // public メンバとしてアクセスできる
                this->y;     // protected メンバとしてアクセスできる
                // this->z;  // アクセスできない
            };
    };

    class Bar : public Foo {
        public:
            Bar() {};

            void bar() {
                this->x;     // public メンバとしてアクセスできる
                this->y;     // protected メンバとしてアクセスできる
                // this->z;  // アクセスできない
            };
    };

}
cpp
foo::Foo f = foo::Foo();
f.x;     // アクセスできる
// f.y   // アクセスできない
// f.z   // アクセスできない

foo::Bar b = foo::Bar();
b.x;     // アクセスできる
// b.y   // アクセスできない
// b.z   // アクセスできない

CBase を Foo が public 継承する場合。CBase で public 宣言されているメンバは Foo にとっても Bar にとっても public。CBase で protected 宣言されているメンバは Foo にとっても Bar にとっても protected。CBase で private 宣言されているメンバは Foo からも Bar からも見えない。

protected

cpp
namespace foo {

    class CBase {
        public:
            CBase() {};

            int x = 10;

        protected:
            int y = 20;

        private:
            int z = 30;
    };

    class Foo : protected CBase {
        public:
            Foo() {};

            void foo() {
                this->x;     // protected メンバとしてアクセスできる
                this->y;     // protected メンバとしてアクセスできる
                // this->z;  // アクセスできない
            };
    };

    class Bar : public Foo {
        public:
            Bar() {};

            void bar() {
                this->x;     // protected メンバとしてアクセスできる
                this->y;     // protected メンバとしてアクセスできる
                // this->z;  // アクセスできない
            };
    };

}
cpp
foo::Foo f = foo::Foo();
// f.x;  // アクセスできない
// f.y;  // アクセスできない
// f.z;  // アクセスできない

foo::Bar b = foo::Bar();
// b.x;  // アクセスできない
// b.y;  // アクセスできない
// b.z;  // アクセスできない

CBase を Foo が protected 継承する場合。CBase で public 宣言されているメンバ x は Foo からは protected に制限されて見える。それに伴って、Foo を public 継承している Bar からも x は protected メンバとして見える。CBase で protected 宣言されているメンバについては、先の例と同様に protected メンバとして見える。CBase で private 宣言されているメンバについては当然 Foo からも Bar からも見えない。

private

cpp
namespace foo {

    class CBase {
        public:
            CBase() {};

            int x = 10;

        protected:
            int y = 20;

        private:
            int z = 30;
    };

    class Foo : private CBase {
        public:
            Foo() {};

            void foo() {
                this->x;     // private としてアクセスできる
                this->y;     // private としてアクセスできる
                // this->z;  // アクセスできない
            };
    };

    class Bar : public Foo {
        public:
            Bar() {};

            void bar() {
                // this->x;  // アクセスできない
                // this->y;  // アクセスできない
                // this->z;  // アクセスできない
            };
    };

}
cpp
foo::Foo f = foo::Foo();
// f.x;  // アクセスできない
// f.y;  // アクセスできない
// f.z;  // アクセスできない

foo::Bar b = foo::Bar();
// b.x;  // アクセスできない
// b.y;  // アクセスできない
// b.z;  // アクセスできない

CBase を Foo が private 継承する場合。CBase で public / protected 宣言されているメンバ x, y は Foo からは private に制限されて見える。それに伴って、Foo を継承している Bar からは x も y も見えなくなる。これは、Foo が CBase を継承していることを派生クラスである Bar に対して隠蔽する効果がある。CBase で private 宣言されているメンバについては当然 Foo からも Bar からも見えない。

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

cpp
namespace foo {

    class CBase {
        public:
            CBase() {};
            CBase(int x) {
                this->x = x;
            };

        private:
            int x;
    };

    class Foo : public CBase {
        public:
            Foo() {};
            Foo(int x) : CBase(x) {
            };
    };

}
cpp
foo::Foo f = foo::Foo();     // 引数なしの Foo::Foo、CBase::CBase が呼ばれる
foo::Foo g = foo::Foo(10);   // 引数ありの Foo::Foo、CBase::CBase が呼ばれる

派生クラスのコンストラクタ宣言の後ろに基本クラスのコンストラクタ呼び出し文を書くことで、オーバーロードされた基本クラスのコンストラクタを特定して呼び出すことができる。指定しない場合は引数なしのコンストラクタが呼ばれる。

派生クラスのポインタは基本クラスのポインタにキャストすることで統一して扱うことができる

cpp
namespace foo {

    class CBase {
        public:
            ...
            virtual void hello() = 0;
            ...
    };

    class Foo : CBase {
        public:
            ...
            void hello();
            ...
    };

    class Bar : CBase {
        public:
            ...
            void hello();
            ...
    };

}
cpp
foo::CBase* a[2];
a[0] = (foo::CBase*)(new foo::Foo());     // 基本クラスのポインタ型にキャスト
a[1] = (foo::CBase*)(new foo::Bar());

for (int i=0; i<2; i++) {
    a[i]->hello();     // foo::Foo と foo::Bar を区別することなく、メソッドを呼べる
}

派生クラスのポインタを基本クラスの型にキャストすると派生クラス独自のメンバにはアクセスできなくなる

cpp
namespace foo {

    class CBase {
        public:
            CBase();
            virtual void common() = 0;
    };

    class Foo : CBase {
        public:
            Foo();
            void common();
            void foo();
    };
}
cpp
foo::CBase* bp;
foo::Foo* f = new foo::Foo();

f->foo();         // 当然呼べる

bp = (foo::CBase*)f;

bp->common();     // これは foo::CBase で宣言されているので呼べる
//bp->foo();      // こっちは foo::CBase では宣言されていないので呼べない

foo::Foo* f2 = (foo::Foo*)bp;
f2->foo();        // foo::Foo* にキャストするとまた呼べるようになる

テンプレート関数

cpp
template <typename T>
T add(T a, T b)
{
    return a + b;
}
cpp
add((int)10, (int)20);
add((double)1.5, (double)1.2);

型名を T として宣言しておいて、関数定義で実際の型名の代わりに T を使うことで、複数の変数型で共通の関数を利用することができる。
テンプレート宣言 template <typename T> を書いた直後の関数宣言にテンプレートが適用される。関数宣言 1 つに対してテンプレート宣言は 1 回しか書けないので、複数の型をテンプレートで表すことはできない。従って、add((int)10, (double)1.5) のような呼び方はできない。

デフォルト引数

cpp
int add(int a, int b=0);

int add(int a, int b)
{
    return a + b;
}

プロトタイプ宣言か関数実体の定義かどちらか一方に書く。デフォルト値として利用できるのは定数や関数等、プログラム実行前に値が決まっているものだけ。
Python3 ではデフォルト値としてなんでも書けるのだが、mutable な値をデフォルト値として使った場合に、実は最初の1回しか評価されないというハマりどころがあった。
これに関しては C++ のほうが分かりやすいかも。

lambda 式

cpp
int x = 10;
std::function<int(int, int)> lambda_func = [](int a, int b){ return a + b; };
int res = lambda_func(10, 20);   // res = 30

この場合、ローカル変数 x は lambda 式の内部からはアクセスできない。x を lambda 式内部から参照したい場合は以下の変数キャプチャ(値キャプチャ)を指定する。

cpp
int x = 10;
std::function<int(int, int)> lambda_func = [=](int a, int b){ return x + a + b; };
int res = lambda_func(10, 20);   // res = 40

変数キャプチャに [=] を指定すると、ローカル変数のコピーが lambda 式内部で参照可能になる。ただし値の変更を行うことはできない。lambda 式内部でローカル変数の値を変更したい場合は以下の変数キャプチャ(参照キャプチャ)を利用する。

cpp
int x = 10;
std::function<int(int, int)> lambda_func = [&](int a, int b){ x = 20; return x + a + b; };
int res = lambda_func(10, 20);   // res = 50
// x = 20

変数キャプチャに [&] を指定すると、lambda 式内部の変数 x は外側の変数 x とまったく同じものになる。従って、lambda_func 実行後の x の値は 20 となる。

23
20
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
20