はうすてんぼぶ

コードかいてて疑問に思ったことや、興味あることをつらつらと暇なときに書く場所、ここはそんな場所

C++の変換コンストラクタについて

C++を勉強してると、ふとした拍子に「何故?」となることが多いので、今日から定期的に書いてくよ。

はじめに

今日は最近あった何故の一つ、変換コンストラクタについて。

class mystring{
private:
    char* str_;
public:
    mystring(const char*str);
    ~mystring();
    const char* toChar() const{return str_;}
}

こんな感じに書いていたときに、

mystring str = "hogehoge";

と何気なく、書いたコードがコンパイルを通り疑問に思ったがきっかけ。

ちょっと調べてみると、原因は、コンストラクタと、その引数に関わっていることが判明した。

どうもある条件が当てはまるときに暗黙的にコンストラクタが呼び出され型を変換してくれることが分かった。

この暗黙的に呼ばれるコンストラクタは「変換コンストラクタ」と呼ばれており、そのことに関してちょっとここにまとめておく。

変換コンストラクタの例

以下の状態のコンストラクタは、変換コンストラクタとして扱われる

  1. コンストラクタの引数が1つである(2つ目以降が引数省略可能なら複数あっても大丈夫)
  2. explicitがコンストラクタについていない
class mystring{
public:
    mystring(const char* str);          //A
    explicit mystring(double d);      //B
    mystring(int i, int length = 0);   //C
    mystring(const char* str1, const char* str2);//D
}
// ....
mystring str = "hoge"; //A ○ 暗黙的な型変換が行われる
mystring str = 1.23;   //B ☓ explicitによる暗黙的な呼び出し禁止
mystring str(1.23);     //B' ○  明示的な呼び出しなら大丈夫
mystring str = 123;    //C ○ デフォルト引数があるため
mystring str = "huga";//D ☓ 引数が2つ以上あるため
explicitとは

explicitとは、「このコンストラクタの暗黙的な呼び出しを禁止する」ということ。
つまり、explicitを付けて宣言しておくと、そのコンストラクタは明示的な呼び出ししかできなくなる。

=に関して

ここでの「=」は、代入演算子ではない。
mystring str = "hoge";は、mystring str("hoge")に変換されているだけで、
operator=(const char*)がこのときに呼ばれることは無い、とのこと。

利点と欠点

この暗黙的に型変換をしてくれるのは、とても便利に見える。
特に上のような型の場合だと、

mystring str = "hoge";

ができるなんて、ああ、なんて理想的なんでしょう。

この変換コンストラクタがやっかいだ、となるのは、引数の型にそのクラスを使った時だと思う。

//宣言側だと何も問題無い
void setString(const mystring& str);

//それを呼び出す側で困惑する
hoge.setString(123);//これが普通に通るなんて、ああ、なんて恐ろしいのでしょう

まとめ

上のように、型変換コンストラクタを使うと(勝手に使わされているだけかもしれないが)、
呼び出す側は、型変換コンストラクタを持つクラスが、引数の型であることを意識せずに使える。
逆(?)に言うと、使えてしまう

これはその関数名にも依るだろうけど、俺は嫌な感じしかしない。

上のmystringであれば、せいぜいAのみを変換コンストラクタとして扱い、他はexplicitを付けておいたほうが安全なのかな。