实现“变体”类

时间:2011-03-15 23:19:46

标签: c++

注意:我知道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()中的动态投射会按预期工作(我不确定)
  • 我应该将T作为const引用返回吗?或者我可以依靠返回值优化来启动吗?
  • 还有其他问题或建议吗?

更新

感谢@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;
};

3 个答案:

答案 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总是完全无误地正确。