きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

Any型の実装

Anyを実装しよう

Any型とは

何でも格納できる型を作ることを考えます。普通はそんな型は必要ないし、当然その分オーバーヘッドが生じてしまうので利用する箇所は限られるだろうけどコンテナに格納するなどして便利なことはあるにはあります。

実装方法

Step1

Any型本体を考えましょう。

class Any {
private:
public:
	template <typename T>
	Any(T const& it) : data(new ###(it)) {

	}

private:
	std::unique_ptr<###> data;
};

とりあえずコンストラクタにデータを入れてそれをどんな方でも保存できなければ要件は満たせません。さて、###にはどんな型を入れればよいのでしょうか。ここはしかたがないのでテンプレートで頑張ることにします。即ち、なんらかのクラステンプレートが必要ということです。

class Any {
private:
	template <typename T>
	class holder {
	public:
		holder(T const& it) : data(it) { }
	private:
		T data;
	};

public:
	template <typename T>
	Any(T const& it) : data(new holder<T>(it)) {

	}

private:
	std::unique_ptr<holder<###>> data;
};

Step2

さてこんどはデータの型が定まらない問題に直面しました。###はどうしたらいいのでしょうか? 複数のクラスを一つのクラスにまとめる…といえば抽象クラスが浮かびます。holderの抽象クラスplace_holderを定義して、holderはこれを継承すれば解決できそうです。

class Any {
private:
	class place_holder {
	public:
		virtual ~place_holder() { }
	};

	template <typename T>
	class holder : public place_holder {
	public:
		holder(T const& it) : data(it) { }
	private:
		T data;
	};

public:
	template <typename T>
	Any(T const& it) : data(new holder<T>(it)) {

	}

private:
	std::unique_ptr<place_holder> data;
};

それでは実際に利用してみましょう。

Any any = 100;
Any any2 = 10.5;

Step3

さて、格納した値を参照することを考えます。今現在の型が何かを知るには実行時に知るしかありません。したがって決め打ちで出力する方法が必要です。place_holderからholderにダウンキャストするにはdynamicなどが便利でしょう。即ち、メンバ関数getを定義し、holderにダウンキャストを試み、ダメであった場合はstd::bad_cast例外を投げ、大丈夫な場合は生データへの参照を返します。

template <typename T>
T& get() const {
	auto p = dynamic_cast<holder<T>*>(data.get());
	if (p == nullptr) {
		throw std::bad_cast();
	}
	return p->data;
}

Step4

再代入やコピー等を考えましょう。他のAnyオブジェクトは当然コピーできますし、任意のオブジェクトはコピーされます。同様に代入もされます。結局次のように成りました。

typeとcloneが必要なのは、typeで型情報を得る時とAnyからAnyへコピーした時にひつようだからです。

class Any {
private:
	struct place_holder {
		virtual ~place_holder() { }
		virtual std::type_info const& type() const { return typeid(nullptr); }
		virtual std::unique_ptr<place_holder> clone() const { return nullptr; }
	};

	template <typename T>
	struct holder : public place_holder {
		holder(T const& it) : data(it) { }
		template <typename ...Args>
		holder(Args ...args) : data(args) { }
		std::type_info const& type() const override { return typeid(data); }
		T data;
		std::unique_ptr<place_holder> clone() const override { return std::make_unique<holder<T>>(data); }
	};

public:
	Any() {}
	template <typename T>
	Any(T const& it) : data(new holder<T>(it)) { }
	Any(Any const& it) : data(std::make_unique<place_holder>(*it.data)) { }
	Any(Any&& it) : data(std::move(it.data)) { }
	Any& operator=(Any const& it) {
		data = it.data->clone();
		return *this;
	}
	Any& operator=(Any&& it) {
		data = std::move(it.data);
		return *this;
	}
	template <typename T>
	Any& operator=(T const& it) {
		data = std::make_unique<holder<T>(it);
		return *this;
	}

	template <typename T>
	T& get() const {
		auto p = dynamic_cast<holder<T>*>(data.get());
		if (p == nullptr) {
			throw std::bad_cast();
		}
		return p->data;
	}

	std::type_info const& type() const {
		return data->type();
	}
private:
	std::unique_ptr<place_holder> data;
};

使う場合はこうします :

Any any = C(); // construct & copy
printf("%s\n", any.type().name());
Any any2;
any2 = any; // copy
printf("%s\n", any2.type().name());
// destruct & destruct