Подтвердить что ты не робот

Внедрение класса "вариант"

Примечание: Я знаю 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? Или могу ли я рассчитывать на оптимизацию возвращаемого значения? "
  • Любые другие проблемы или предложения?

Update

Благодаря @templatetypedef для его предложений. Эта обновленная версия использует dynamic_cast для проверки соответствия типов. Несоответствия типов, вызванные различиями в константе, теперь исключаются благодаря классам TypeWrapper (которые я бесстыдно украл из проекта Poco С++).

Итак, это текущая версия. Вероятно, он содержит несколько ошибок, поскольку я не знаком с идеей изменения 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;
};
4b9b3361

Ответ 1

Эта реализация близка к правильной, но похоже, что она содержит несколько ошибок. Например, этот код:

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, которое было тщательно отлажено и протестировано. Тем не менее, поздравляю вас с выяснением ключевого трюка, который делает эту работу!

Ответ 2

Рискуя предоставить не-ответ, так как вы уже используете Boost, я рекомендую вам попробовать Boost.Variant или Boost.Any вместо того, чтобы выполнять собственную реализацию.

Ответ 3

Лучше использовать std::auto_ptr, так как не требуется семантики подсчета ссылок. Обычно я возвращаюсь по ссылке, так как совершенно законно изменять значение внутри или по указателю, чтобы разрешить NULL.

Вы должны использовать dynamic_cast для соответствия типам, а не typeid(), и вы можете просто использовать Boost. typeid() кажется, что он должен это обеспечить, но на самом деле это не из-за его открытости, тогда как dynamic_cast всегда точно и однозначно корректно.