一、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;
}