注意:我知道boost::variant
,但我很好奇设计原则。这个问题主要是为了自我教育。
在我目前的工作中,我发现了一个旧的变体类实现。它使用union
实现,并且只能支持少数几种数据类型。我一直在考虑如何设计改进版本。经过一些修修补补后,我得到了似乎有用的东西。但是我想知道你对它的看法。这是:
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include <boost/shared_ptr.hpp>
class Variant
{
public:
Variant() { }
template<class T>
Variant(T inValue) :
mImpl(new VariantImpl<T>(inValue)),
mClassName(typeid(T).name())
{
}
template<class T>
T getValue() const
{
if (typeid(T).name() != mClassName)
{
throw std::logic_error("Non-matching types!");
}
return dynamic_cast<VariantImpl<T>*>(mImpl.get())->getValue();
}
template<class T>
void setValue(T inValue)
{
mImpl.reset(new VariantImpl<T>(inValue));
mClassName = typeid(T).name();
}
private:
struct AbstractVariantImpl
{
virtual ~AbstractVariantImpl() {}
};
template<class T>
struct VariantImpl : public AbstractVariantImpl
{
VariantImpl(T inValue) : mValue(inValue) { }
~VariantImpl() {}
T getValue() const { return mValue; }
T mValue;
};
boost::shared_ptr<AbstractVariantImpl> mImpl;
std::string mClassName;
};
int main()
{
// Store int
Variant v(10);
int a = 0;
a = v.getValue<int>();
std::cout << "a = " << a << std::endl;
// Store float
v.setValue<float>(12.34);
float d = v.getValue<float>();
std::cout << "d = " << d << std::endl;
// Store map<string, string>
typedef std::map<std::string, std::string> Mapping;
Mapping m;
m["one"] = "uno";
m["two"] = "due";
m["three"] = "tre";
v.setValue<Mapping>(m);
Mapping m2 = v.getValue<Mapping>();
std::cout << "m2[\"one\"] = " << m2["one"] << std::endl;
return 0;
}
输出正确:
a = 10
d = 12.34
m2["one"] = uno
我的问题是:
getValue()
中的动态投射会按预期工作(我不确定)感谢@templatetypedef的建议。此更新版本仅使用dynamic_cast
来检查类型是否匹配。由于TypeWrapper类(我从Poco C ++项目中无耻地窃取),现在可以避免由const的差异引起的类型不匹配。
所以这是当前版本。它可能包含一些错误,因为我不熟悉修改模板模板上的const / ref的想法。明天我会有一个新的面貌。
template <typename T>
struct TypeWrapper
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<const T>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<const T&>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
template <typename T>
struct TypeWrapper<T&>
{
typedef T TYPE;
typedef const T CONSTTYPE;
typedef T& REFTYPE;
typedef const T& CONSTREFTYPE;
};
class Variant
{
public:
Variant() { }
template<class T>
Variant(T inValue) :
mImpl(new VariantImpl<typename TypeWrapper<T>::TYPE>(inValue))
{
}
template<class T>
typename TypeWrapper<T>::REFTYPE getValue()
{
return dynamic_cast<VariantImpl<typename TypeWrapper<T>::TYPE>&>(*mImpl.get()).mValue;
}
template<class T>
typename TypeWrapper<T>::CONSTREFTYPE getValue() const
{
return dynamic_cast<VariantImpl<typename TypeWrapper<T>::TYPE>&>(*mImpl.get()).mValue;
}
template<class T>
void setValue(typename TypeWrapper<T>::CONSTREFTYPE inValue)
{
mImpl.reset(new VariantImpl<typename TypeWrapper<T>::TYPE>(inValue));
}
private:
struct AbstractVariantImpl
{
virtual ~AbstractVariantImpl() {}
};
template<class T>
struct VariantImpl : public AbstractVariantImpl
{
VariantImpl(T inValue) : mValue(inValue) { }
~VariantImpl() {}
T mValue;
};
boost::shared_ptr<AbstractVariantImpl> mImpl;
};
答案 0 :(得分:9)
这个实现接近正确,但看起来它有一些错误。例如,此代码:
if (typeid(T).name() != mClassName)
无法保证正常工作,因为.name()
中的type_info
函数无法保证为每种类型返回唯一值。如果你想检查类型是否匹配,你应该使用这样的东西:
if (typeid(*mImpl) == typeid(VariantImpl<T>))
更准确地检查类型是否匹配。当然,您需要注意const
个问题,因为存储const T
并存储T
会产生不同的类型。
至于您关于dynamic_cast
的问题,在您描述的情况下,您不需要使用dynamic_cast
,因为您已经检查过确认类型是否匹配。相反,您可以使用static_cast
,因为您已经发现了类型错误的情况。
更重要的是,你在这里定义的是一个“无限制变体”,它可以保留任何东西,而不仅仅是一小部分限制类型(这是你通常在变体中找到的)。虽然我非常喜欢这段代码,但我建议使用像Boost.Any或Boost.Variant这样的东西,它已经过广泛的调试和测试。那就是说,恭喜找出使这项工作成功的关键技巧!
答案 1 :(得分:4)
冒着提供无应答的风险,因为您已经在使用Boost,我建议您尝试Boost.Variant或Boost.Any,而不是滚动您自己的实现。
答案 2 :(得分:3)
最好使用std::auto_ptr
,因为不需要引用计数语义。我通常会通过引用返回,因为更改其中的值或通过允许NULL的指针是完全合法的。
您应该使用dynamic_cast
来匹配类型,而不是typeid()
,您可以使用Boost。 typeid()
似乎应该提供这个,但实际上它并不是因为它的规范的开放性,而dynamic_cast
总是完全无误地正确。