2012年9月25日火曜日

C++のexplicit

たまに見かけて何だっけ? と思うシリーズ。日常的にC++で書いている人にexplicitは常識かもしれないけど、温いLLかダサいHDLばかり触っていると、ついググってしまうのよね。自戒の意味も込めて、自分の手を動かしてまとめる。

さて本題。explicitはコンストラクタに付けられる修飾子で、こいつが付けられたコンストラクタはimplicitに呼ばれなくなる。では、そもそもどんな場面で、陰に陽にコンストラクタは呼ばれているのか。サンプルコードを走らせてみれば一目瞭然。

#include <iostream>
using namespace std;
class TestClass1 {
    int val;
public:
    TestClass1() : val(0) {
        cout << "default constructor: val=" << val << endl;
    }
    TestClass1(int v) : val(v) {
        cout << "constructor: val=" << val << endl;
    }
    TestClass1& operator=(const TestClass1& rhs) {
        if (this != &rhs) val = rhs.val;
        cout << "assignment operator: val=" << val << endl;
        return *this;
    }
};
void test1() {
    cout << "[TestClass1]" << endl;
    TestClass1 t1 = TestClass1(10);
    TestClass1 t2 = 20;
    TestClass1 t3;
    t3 = 30;
}
class TestClass2 {
    int val;
public:
    TestClass2() : val(0) {
        cout << "default constructor: val=" << val << endl;
    }
    explicit TestClass2(int v) : val(v) {
        cout << "constructor: val=" << val << endl;
    }
    TestClass2& operator=(const TestClass2& rhs) {
        if (this != &rhs) val = rhs.val;
        cout << "assignment operator: val=" << val << endl;
        return *this;
    }
};
void test2() {
    cout << "[TestClass2]" << endl;
    TestClass2 t1 = TestClass2(10);
//  TestClass2 t2 = 20;             // ERROR
    TestClass2 t3;
//  t3 = 30;                        // ERROR
}
class TestClass3 {
    int val;
public:
    TestClass3() : val(0) {
        cout << "default constructor: val=" << val << endl;
    }
    explicit TestClass3(int v) : val(v) {
        cout << "constructor: val=" << val << endl;
    }
    TestClass3(double v) : val(v) {
        cout << "constructor with double: val=" << val << endl;
    }
    TestClass3& operator=(const TestClass3& rhs) {
        if (this != &rhs) val = rhs.val;
        cout << "assignment operator: val=" << val << endl;
        return *this;
    }
};
void test3() {
    cout << "[TestClass3]" << endl;
    TestClass3 t1 = TestClass3(10);
    TestClass3 t2 = 20;
    TestClass3 t3;
    t3 = 30;
}
int main() {
    test1();
    test2();
    test3();
    return 0;
}

明示的に呼び出しているところ以外でも、オブジェクトの初期化や、コピー代入のコピー元一時オブジェクトを作るところで、黙ってコンストラクタが呼ばれる。

TestClass1 t2 = 20;
のような記述はパッと見では型が一致しないように見えるが、右辺値を引数としてコンストラクタが呼ばれる。便利ではあるけれどミスの元になりそうなこの機能を抑止するのがexplicit。

ただしTestClass3のように、暗黙の型変換をすれば呼び出せる別のimplicitなコンストラクタがあれば、それが呼ばれてしまう。

0 件のコメント:

コメントを投稿