使用模板技巧实现Variant

本文详细介绍了在Qt中使用QVariant类保持任意类型数据的技巧,包括其无需指定类型构造、底层实现原理,以及通过模板和类模板继承实现的Variant方法。展示了如何通过void*指针和DataRelease模板类来保存和释放不同类型的数据副本。
摘要由CSDN通过智能技术生成

一、QVariant使用

        在Qt中可以使用QVaraint类保持任意类型的数据,使用非简单。QVaraint使用简单的一个重要原因是在用数据构造QVaraint时,无需指定类型去构造。像tuple在构造时,需要指定类型,如:tuple<类型> t,如果类型非常复杂,那么构造的时候非常复杂,使用起来也就不方便,真是这样数std才提供了make_tuple取构造。auto t = make_tuple(1)。

QVaraint v(9);
A a;
v = a;
v.setValue("sdfs");
cout << v.getValue<const char *>() << endl;

        如果取查看QVaraint实现源码,发现非常复杂。本文介绍一种使用模板技巧实现Variant方法。

二、Varaint实现分析

         从上面QVaraint使用得知,QVariant在构造时并未使用类模板形式,QVariant是类。要使得Varaint保持数据,需要保持数据的副本,但是QVariant并不是类模板,所以无法通过类型的对象去保持副本。所以只能通过通用的void *去保持数据副本的指针。如下:

#define _Type_(T) std::remove_reference<T>::type    // T的去引用类型,T有可能为T&,T&&

class _Variant 
{
public:
	template<typename T>
	_Variant(T &&t)
	{
		ptr = new _Type_(T)(t);
	}

private:
	void *ptr = nullptr;
};
  • _Type_宏定义通过std::remove_reference类模板去掉类型引用以获得原类型;
  • 使用模板构造函数通过ptr = new _Type_(T)(t)获取数据副本的指针;
  • 模板构造函数参数类为通用引用,既可以接受左值也可以接受右值。

现在可以通过void *ptr去保存数据副本,必须在_Varaint析构函数中要删除副本内存。可是不能通过delete ptr进行删除,因为ptr类型不是原本类型,这样删除会有内存泄漏。那么如何在析构函数中删除副本内存呢?这里有一个非常重要的技巧,也是_Varaint实现的核心。就是使用类模板去保存类类型,通过模板继承删除副本。具体如下:

#define _Type_(T) std::remove_reference<T>::type    // T的去引用类型,T有可能为T&,T&&

class PointerRelease_impl
{
public:
	virtual void release(void *&) = 0;
};

template<typename T>
class DataRelease : public PointerRelease_impl
{
public:
	virtual void release(void *&ptr) override
	{
		delete (_Type_(T)*)(ptr);
		ptr = nullptr;
		std::cout << "~release~" << endl;
	}
};

class _Variant 
{
public:
	template<typename T>
	_Variant(T &&t)
	{
		ptr = new _Type_(T)(t);
		m_pRelease = new DataRelease<T>();  // 用DataRelease来保持类型
	}
	~_Variant()
	{
		m_pRelease->release(ptr);
	}
private:
	void *ptr = nullptr;
	PointerRelease_impl *m_pRelease = nullptr;
};
  • 定义了一个指针删除的接口类PointerRelease_impl,在_Varaint中定义了一个PointerRelease_impl的指针m_pRelease,提供接口virtual void release(void *&) = 0;在析构函数中通过调用release(ptr)删除副本内存;
  • PointerRelease是继承PointerRelease_impl的类模板,可以用于保存数据类型。在_Variant构造函数中m_pRelease的创建使用PointerRelease进行创建,可以在构造的时候就将数据类型传到PointerRelease中;
  • 在PointerRelease中release实现中同时知道数据类型和数据指针ptr,因此可以通过delete (_Type_(T)*)(ptr);删除对象指针。

 通过类模板继承的方式可以保存数据类型,从而延缓对数据类型的使用。

三、完整代码 

        _Varaint类完整代码实现如下:

#define _Type_(T) std::remove_reference<T>::type    // T的去引用类型,T有可能为T&,T&&

class PointerRelease_impl
{
public:
	virtual void release(void *&) = 0;
};

template<typename T>
class DataRelease : public PointerRelease_impl
{
public:
	virtual void release(void *&ptr) override
	{
		delete (_Type_(T)*)(ptr);
		ptr = nullptr;
	}
};

class _Variant
{
public:
	_Variant():strTypename(""){}

	template<typename T>
	_Variant(T &&t)
	{
		_init_(std::forward<T>(t));
	}

	_Variant(const char *s)
	{
		_init_(std::string(s));
	}

	template<typename T>
	_Variant & operator = (T &&t)
	{
		if (this != (void *)(&t))
		{
			// 先释放ptr
			if (ptr != nullptr && m_pRelease != nullptr)
			{
				m_pRelease->release(ptr);
			}

			// 在根据t重新初始化
			_init_(std::forward<T>(t));
		}
		return *this;
	}

	_Variant & operator = (const char *s)
	{
		// 先释放ptr
		if (ptr != nullptr && m_pRelease != nullptr)
		{
			m_pRelease->release(ptr);
		}

		// 在根据t重新初始化
		_init_(std::string(s));

		return *this;
	}

	template<typename T>
	T getValue()
	{
		if (ptr != nullptr &&
			strcmp(typeid(T).name(), strTypename) == 0)
		{
			return *(T*)(ptr);
		}
		else return T();
	}

	template<>
	const char * getValue<const char *>()
	{
		if (ptr != nullptr &&
			strcmp(typeid(std::string).name(), strTypename) == 0)
		{
			return (*static_cast<std::string*>(ptr)).c_str();
		}
		else return "";
	}

	template<typename T>
	void setValue(T &&t)
	{
		*this = t;
	}
	void setValue(const char *s)
	{
		*this = s;
	}

	~_Variant()
	{
		m_pRelease->release(ptr);
	}

private:
	template<typename T>
	void _init_(T &&t)
	{
		strTypename = typeid(_Type_(T)).name();
		ptr = new _Type_(T)(t);      // 原来值副本指针
		m_pRelease = new DataRelease<T>();  // 用DataRelease来保持类型
	}

private:
	void *ptr = nullptr;
	const char *strTypename;
	PointerRelease_impl *m_pRelease = nullptr;
};

现在我们可以使用_Variant类,如下:

int main()
{
	_Variant v("sdfsf");
	cout << v.getValue<const char *>() << endl;

	A a;
	v = a;

	v = 9;
	cout << v.getValue<int>() << endl;

	v.setValue(90.3);
	cout << v.getValue<double>() << endl;

	v = "sdsfwef";
	cout << v.getValue<const char *>() << endl;

	v.setValue("sdfsf");
	cout << v.getValue<const char *>() << endl;

	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值