#24(の1) すべての引数に型変換が必要なら,非メンバ関数を定義する.

ということで,Effective C++24項の内容です.

コンストラクタと暗黙の型変換

Effective CPPでは,有理数の例を用いて暗黙の型変換が好まれる例を説明していました.他に簡単&適切な例が思いつかないので,ここでも有理数の例を用いて実験したいと思います.まず,下記のコードを書いた時になにが起こっているかを見てみます.


#include <iostream>
#include <string>

class Rational {
public:
  Rational(int numerator=0, int denominator=1) 
   : m_numerator(numerator), m_denominator(denominator) 
  { std::cout << "Rational(int numerator, int denominator)" << std::endl; }

  ~Rational()
  { std::cout << "~Rational()" << std::endl; }

  Rational(const Rational &rhs) {
    this->m_numerator = rhs.m_numerator;
    this->m_denominator = rhs.m_denominator;
    std::cout << "Rational(const Rational &rhs)" << std::endl;
  }

  Rational& operator=(const Rational& rhs) {
    std::cout << "operator=(const Rational& rhs)" << std::endl;
    m_numerator = rhs.m_numerator;
    m_denominator = rhs.m_denominator;
    return *this;
  }

  int m_numerator;
  int m_denominator;
};

int main(int, char**) {
  std::cout << "chap_4th_24_1.cpp" << std::endl;

  Rational r = 100;

  std::cout << "r.m_numerator : " << r.m_numerator << std::endl;
  std::cout << "r.m_denominator : " << r.m_denominator << std::endl;

  return 0;
}

上記サンプルコードの実行結果は下記のようになります.

chap_4th_24_1.cpp
Rational(int numerator, int denominator)
r.m_numerator : 100
r.m_denominator : 1
~Rational()

明示的にコンストラクタを呼んでいませんが,”Rational r = 100” の部分でコンストラクタが暗黙的に呼ばれています.で,実際問題どのようにこれが起こっているかというと...
1.Rationalクラスのオブジェクト r が整数100で初期化されようとしている.
2.しかし,Rationalクラスと整数100の型が異なるため,コピーコンストラクタは呼び出せず,整数100からRationalを生成する方法をコンパイラが探す.
3.Rationalクラスのコンストラクタでは,デフォルト引数が指定されているため,numeratorにだけ100を指定すればRationalクラスをRational(100, 1)として生成する.(<-暗黙の型変換)

で,コンストラクタにexplicitと書けばこの暗黙の型変換がされなくなります.

#include <iostream>
#include <string>

class Rational {
public:
  explicit Rational(int numerator=0, int denominator=1) 
   : m_numerator(numerator), m_denominator(denominator) 
  { std::cout << "Rational(int numerator, int denominator)" << std::endl; }

  ~Rational()
  { std::cout << "~Rational()" << std::endl; }

  Rational(const Rational &rhs) {
    this->m_numerator = rhs.m_numerator;
    this->m_denominator = rhs.m_denominator;
    std::cout << "Rational(const Rational &rhs)" << std::endl;
  }

  Rational& operator=(const Rational& rhs) {
    std::cout << "operator=(const Rational& rhs)" << std::endl;
    m_numerator = rhs.m_numerator;
    m_denominator = rhs.m_denominator;
    return *this;
  }

  int m_numerator;
  int m_denominator;
};

int main(int, char**) {
  std::cout << "chap_4th_24_2.cpp" << std::endl;

  // コンストラクタが explicit として宣言されたので,下記はコンパイルエラー.
  //Rational r = 100;

  // コンストラクタが明示的に呼ばれているので,これはOK!
  Rational r(100);

  std::cout << "r.m_numerator : " << r.m_numerator << std::endl;
  std::cout << "r.m_denominator : " << r.m_denominator << std::endl;

  return 0;
}

上記のサンプルコードでは,Rational r = 100ではもはやコンパイルが通らなくなりました.代わりにコンストラクタを明示的に呼び出した "Rational r(100)" はコンパイルが通ります.
ということで,次回のエントリに続きます...