読者です 読者をやめる 読者になる 読者になる

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

終了処理をするメンバ関数の違いを吸収してくれるカスタムデリータ

C++

はじめに

次のような微妙に異なる終了処理が必要なクラスが沢山あるときに、それぞれに対して別々のカスタムデリータを用意するのは面倒です。

struct Hoge 
{
	Hoge() {}
	~Hoge() {}
	void close() {
		std::cout << "Hoge::close" << std::endl;
	};
};

struct Fuga
{
	Fuga() {}
	~Fuga() {}
	void close(int i) {
		std::cout << "Fuga::close(int)" << std::endl;
	};
};

これを共通のカスタムデリータで処理したくて調べてみました。

コード

#include <iostream>
#include <memory>
#include <type_traits>

// クラス T がメンバ関数 close() を持つか調べる
template <typename T>
struct has_memfun_close_void
{
private:
	template<typename U>
	static auto check( U v ) -> decltype( v.close(), std::true_type() );
	static auto check( ... ) -> decltype( std::false_type() );
public:
	typedef decltype( check( std::declval<T>() ) ) type;
	static const bool value = type::value;
};

// クラス T がメンバ関数 close(int) を持つか調べる
template <typename T>
struct has_memfun_close_int
{
private:
	template<typename U>
	static auto check( U v ) -> decltype( v.close(0), std::true_type() );
	static auto check( ... ) -> decltype( std::false_type() );
public:
	typedef decltype( check( std::declval<T>() ) ) type;
	static const bool value = type::value;
};

// SFINAE でメンバ関数の有無に応じて処理を場合分け
struct custom_deleter
{
	template <typename T>
	void operator()(T* p, typename std::enable_if<has_memfun_close_void<T>::value, T>::type* = 0) const
	{
		p->close();
		delete p;
	}

	template <typename T>
	void operator()(T* p, typename std::enable_if<has_memfun_close_int<T>::value, T>::type* = 0) const
	{
		p->close(100);
		delete p;
	}
};

struct Hoge 
{
	Hoge() {}
	~Hoge() {}
	void close() {
		std::cout << "Hoge::close" << std::endl;
	};
};

struct Fuga
{
	Fuga() {}
	~Fuga() {}
	void close(int i) {
		std::cout << "Fuga::close" << std::endl;
	};
};

int main(int argc, char const* argv[])
{
	{
		std::shared_ptr<Hoge> hoge(new Hoge(), custom_deleter());
		std::shared_ptr<Fuga> fuga(new Fuga(), custom_deleter());
	}
	return 0;
}

enable_if に false_type が引き渡されると struct {} が返ってくるので、type をもっていないことから SFINAE で別のオーバーロード関数を探しに行く感じです。便利。
Visual Studio だと declval が無かったのですが、今回の例では「std::declval()」でなく「T()」でも動きます。

おわりに

もっと良い方法あったら教えて下さい (-人-)