忘记当初具体想做什么了,只记得要求:将各种类型(如int,char*,float,自定义类型等)放到一个容器里。这样的要求,目前stl的任何容器都是做不到的。因为容器仅仅能够存储同一种类型的对象,因此我们只能往代理的方向去考虑。一提到代理,大家首先想到的可能是继承:设计一个基类(如AnyType),其他的类型均从基类派生(如StringType,IntType,FloatType),容器里存储存储基类指针,设计如下:
该种方案确实从一定程度上解决了方案,但却不是一个很好的方案,原因如下:
1.扩展性很差,容器中想容纳任何类型,都得从NullType派生一个新的类型,已做适配;
2.容器中存的是基类指针,涉及到动态管理内存,申请释放内存又是令人头大的问题。
那么,还有没有其他方案呢?有——模板类:设计一个模板类,让模板类存储实际对象,容器存储模板对象,如下:
但是仔细一想,这供方案更令人沮丧。模板是编译时多态,在编译时AnyType<int>和AnyType<float>的具体类型在编译时就已确定,它们属于不同的类型,怎么可能存在同一个容器里呢?
既然单独使用继承和模板都不能完美的解决问题,那么把二者结合起来呢:定义一个基类Content,用于定义指针,以便存储;从Content派生模板类ContentImpl<T>,用于实现具体类型;AnyType存储Content指针,并负责维护Content指针指向对象的初始化与销毁,如下:
方案看似可行,接下来我们考虑下具体实现:允许构造空对象,默认构造函数不可少;需要用具体类型去构造一个AnyType对象,因此一个以具体类型对象作为参数的构造函数不可少;具体对象的获取,使用成员函数还是类型转换(全局函数),个人觉得亮着都一样,只不过使用成员函数看上去怪怪的,但使用全局函数不要忘记设置友元函数;具体对象返回指针还是引用,肯定是指针,通过nullptr可以判断对象是否有效;拷贝构造函数;赋值函数......最后得出下面的结果:
class AnyType
{
public:
AnyType(void) : m_ContentPtr(nullptr) {}
template <typename T>
AnyType(const T &val) : m_ContentPtr(new ContentImpl<T>(val)) {}
AnyType(const AnyType &other) : m_ContentPtr(other.m_ContentPtr == nullptr ? nullptr : other.m_ContentPtr->Clone()) {}
template <typename T, typename std::enable_if<false, std::is_same<AnyType &, T>>::value>
AnyType(T &&val) : m_ContentPtr(new ContentImpl<typename std::decay<T>::type>(static_cast<T &&>(val))) {}
AnyType(AnyType &&other) : m_ContentPtr(other.m_ContentPtr) { other.m_ContentPtr = nullptr; }
~AnyType(void)
{
if (m_ContentPtr != nullptr) {
delete m_ContentPtr;
m_ContentPtr = nullptr;
}
}
AnyType &operator=(const AnyType &rhs)
{
if (&rhs == this)
return *this;
delete m_ContentPtr;
m_ContentPtr = rhs.m_ContentPtr->Clone();
return *this;
}
bool IsNull(void) const { return m_ContentPtr == nullptr; }
const std::type_info &Type(void) { return m_ContentPtr == nullptr ? typeid(void) : m_ContentPtr->Type(); }
template <typename T>
T *Get(void) { return m_ContentPtr == nullptr ? nullptr : m_ContentPtr->Type() == typeid(T) ? &static_cast<ContentImpl<T> *>(m_ContentPtr)->m_Value : nullptr; }
private:
class Content
{
public:
virtual ~Content(void) {}
virtual const std::type_info &Type(void) = 0;
virtual Content *Clone(void) = 0;
};
template <typename T>
class ContentImpl final : public Content
{
public:
ContentImpl(const T &val) : m_Value(val) {}
ContentImpl(T &&val) : m_Value(static_cast<T &&>(val)) {}
const std::type_info &Type(void) override { return typeid(m_Value); }
Content *Clone(void) override { return new ContentImpl(m_Value); }
ContentImpl &operator=(const ContentImpl &) = delete;
public:
T m_Value;
};
Content *m_ContentPtr;
template <typename T>
friend T *AnyTypeCast(AnyType *operand);
};
template <typename T>
inline T *AnyTypeCast(AnyType *operand)
{
return operand->Type() == typeid(T) ? &static_cast<AnyType::ContentImpl<T> *>(operand->m_ContentPtr)->m_Value : nullptr;
}
是不是很熟悉?其实这就是boost::any的设计思想。注意上面的代码用到了c++14的特性,vs2017编译能够通过,其他开发环境未曾尝试。注意构造函数AnyType(T &&val)的实现,必须在模板参数中添加“typename std::enable_if<false, std::is_same<AnyType &, T>>::value”,意思是如果类型模板类型T为AnyType,则不允许调用该构造函数。如果没有该句,使用AnyType非常量对象构造AnyType对象时,不会执行AnyType的拷贝构造函数,而执行AnyType(T &&val),具体原因不知道。如果读者知道,欢迎留言。