问题描述
注意:我知道 boost :: variant
,但我很好奇设计原则。这个问题主要是为了自我教育。
原始帖子
在我目前的工作,实施。它实现与联合
,并且只能支持一些数据类型。我一直在想如何设计一个改进的版本。经过一些修补,我结束了似乎工作的东西。但我想知道你对它的看法。这是:
#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(匹配类型!
}
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()
{
//存储int
变体v(10);
int a = 0;
a = v.getValue< int>();
std :: cout<< a =<< a<< std :: endl;
//存储float
v.setValue< float>(12.34);
float d = v.getValue< float>();
std :: cout<< d =<< d<< std :: endl;
//存储地图< string,string>
typedef std :: map< std :: string,std :: string>映射;
映射m;
m [one] =uno;
m [two] =到期;
m [three] =tre;
v.setValue< Mapping>(m);
映射m2 = v.getValue< Mapping>();
std :: cout<< m2 [\one\] =< m2 [一]< std :: endl;
return 0;
}
输出正确:
a = 10
d = 12.34
m2 [one] = uno
我的问题是:
- 此实施是否正确?
-
getValue()
中的动态转换会按预期工作(我不确定) - T作为const引用?
- 任何其他问题或建议?
更新
感谢@templatetypedef的建议。此更新版本仅使用 dynamic_cast
来检查类型是否匹配。类型不匹配引起的不稳定性现在避免了由于TypeWrapper类(我无耻地从Poco C + +项目偷)。
所以这是当前版本。它可能包含一些错误,因为我不熟悉修改模板模板的const / ref的想法。明天我会有新的样子。
模板< 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
};
这个实现接近正确,它有几个错误。例如,此代码:
if(typeid(T).name()!= mClassName)
无法保证正常工作,因为 .name()
type_info
不保证为每个类型返回唯一值。如果你想检查类型是否匹配,你应该使用这样:
if(typeid(* mImpl)= = typeid(VariantImpl< T>))
更准确地检查类型是否匹配。当然,你需要注意 const
问题,因为存储 const T
并存储<$ c $
至于你对 dynamic_cast
的问题,在你描述的情况下,你不需要使用 dynamic_cast
,因为你已经有一个检查,以确认类型匹配。相反,你可以使用 static_cast
,因为你已经遇到了你错误类型的情况。
Note: I'm aware of boost::variant
, but I am curious about the design principles. This question mostly for self-education.
Original post
At my current job I found an old variant class implementation. It's implemented with a union
and can only support a handful of datatypes. I've been thinking about how one should go about designing an improved version. After some tinkering I ended up with something that seems to work. However I'd like to know your opinion about it. Here it is:
#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;
}
Output is correct:
a = 10
d = 12.34
m2["one"] = uno
My SO questions are:
- Is this implementation correct?
- Will the dynamic cast in
getValue()
work as expected (I'm not certain) - Should I return T as a const reference instead? Or can I count on return-value-optimization to kick in?
- Any other problems or suggestions?
Update
Thanks to @templatetypedef for his suggestions. This updated version only uses dynamic_cast
to check if the types match. Type mismatches caused by differences in constness are now avoided thanks to the TypeWrapper classes (which I have shamelessly stolen from the Poco C++ project).
So this is the current version. It's likely to contain a few errors though, as I'm not familiar with the idea of modifying const/ref on template templates. I'll have a fresh look tomorrow.
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;
};
This implementation is close to correct, but it looks like it has a few bugs. For example, this code:
if (typeid(T).name() != mClassName)
is not guaranteed to work correctly because the .name()
function in type_info
is not guaranteed to return a unique value for each type. If you want to check if the types match, you should probably use something like this:
if (typeid(*mImpl) == typeid(VariantImpl<T>))
Which more accurately checks if the type matches. Of course, you need to watch out for const
issues, since storing a const T
and storing a T
will yield different types.
As for your question about dynamic_cast
, in the case you've described you don't need to use the dynamic_cast
because you already have a check to confirm that the type will match. Instead, you can just use a static_cast
, since you've already caught the case where you have the wrong type.
More importantly, though, what you've defined here is an "unrestricted variant" that can hold absolutely anything, not just a small set of restricted types (which is what you'd normally find in a variant). While I really like this code, I'd suggest instead using something like Boost.Any or Boost.Variant, which has been extensively debugged and tested. That said, congrats on figuring out the key trick that makes this work!
这篇关于实施“变体”类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!