オブジェクトの初期化と代入

ライブラリを書いていると、自作のクラスを便利に使えるようにコンストラクタ・コピーコンストラクタ・コピー代入演算子をたくさん用意したくなることがあります。でも、これらが一体いつどこで呼ばれるのか資料が少なかったので、また仕様書を読んでみました。そんなわけで以下メモです。

注:この記事ではunionやPOD・非PODの区別は書いていません。

初期化と代入文

まず大原則として、初期化と代入文は全くの別物として扱われます。例えば、下のコードの3行目は代入文のように見えますが、これは単に初期化の表記に=記号を使っているだけで、代入文ではありません。

T t;		//初期化子のない初期化
T t(1.0);	//直接の初期化
T t = u;	//コピー表記の初期化
t = u;		//代入文

ここではそれぞれの構文の区別のために、コメントに書いた名前で呼びたいと思います。

初期化について

初期化子のない初期化では、クラスの場合はコンストラクタが呼ばれ、それ以外では不確定な値を取ります。

直接の初期化はT t(1.0)のような場合の他に、T(1.0)のような書き方・new・static_cast・メンバ初期化子による初期化時にも行われます。また、コピー表記の初期化はT t = u;のような場合の他に、関数の引数・関数の戻り値・例外送出・例外ハンドリング・{}を使った初期化のときにも行われます。

クラスを初期化するとき、直接の初期化の場合と「コピー表記の初期化で、両方が同じ型であるか、ある型をその派生した型で初期化する場合」に対しては対応するコンストラクタが直接呼ばれます。それ以外のクラスの初期化では、対象の型へのユーザ定義の変換がされた後、対応するコンストラクタが呼ばれます。

非クラスをクラスで初期化する場合には、変換関数が呼ばれます。それ以外の場合は、標準の型変換が行われます。

代入文について

代入文では、クラスへの代入にはコピー代入演算子が使用され、それ以外では暗黙の標準型変換が使用されます。

一時オブジェクト

コンパイラは、型変換や関数の引数の初期化・戻り値のための一時オブジェクトの生成を省略することができます。しかし、コンパイラによって一時オブジェクトの生成が省略されると分かっていたとしても、生成時に使用されるはずのコンストラクタのような、一時オブジェクトの生成に必要な要素を省略することはできません。

まとめ

コピー代入演算子が呼ばれるのは、プログラマが直に代入文を書いたときや、直接演算子を呼んだときぐらいしかありません。それ以外の初期化や言語機能上コピーが必要になる場面では、全てコンストラクタが使われます。

テンプレート関数のオーバーロード解決とSFINAE

自作のライブラリを書いていて、SFINAE(Substitution failure is not an error)の詳細が知りたくなった。仕様書を読んだので以下にメモ。

テンプレート関数のオーバーロード解決規則

あるテンプレート関数の呼び出しに対して、オーバーロードされた関数の集合が決定される。それは、

  • 同名の普通の関数
  • 同名のテンプレート関数のうち、テンプレート引数への代入が成功する関数の、その代入後のモノ

を合わせたものになる。「代入後のモノ」は普通の関数と同様に扱われるので、この集合全体が通常のオーバーロード解決にかけられる。
また、

  • テンプレート引数への代入は関数のシグネチャだけを見て行われる
  • 代入に失敗したものは単純に候補に入らないだけなので、エラーにはならない(これがSFINAE)

とのこと。

C++でURLエンコード/パーセントエンコード

ネットで見つけたものがうまく動かなかったから自分で書いた。space_to_plusをtrueにするとスペースが(%20ではなく)+記号になるようにした。

#include <string>
#include <sstream>
#include <iomanip>

std::string url_encode(const std::string& str, bool space_to_plus = false) {
	std::ostringstream buf;
	buf << std::hex << std::setfill('0');
	
	typedef std::string::const_iterator Iterator;
	Iterator end = str.end();
	for(Iterator itr = str.begin(); itr != end; ++itr) {
		if((*itr >= 'a' && *itr <= 'z') || (*itr >= 'A' && *itr <= 'Z') || (*itr >= '0' && *itr <= '9') ||
			*itr == '-' || *itr == '.' || *itr == '_' || *itr == '~') {
			buf << *itr;
		} else if(space_to_plus && *itr == ' ') {
			buf << '+';
		} else {
			buf << '%' << std::setw(2) << static_cast<int>(static_cast<unsigned char>(*itr));
		}
	}
	return buf.str();
}