Variant или any

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 7 мес., 1 неделя назад.

  • Автор
    Сообщения
  • #3613

    «Variant» это такой объект-контейнер, в который можно поместить любой другой объект любого типа, а сами «варианты» можно сложить в массив, поскольку с точки зрения языка они явлются одним и тем же типом, независимо от того, что внутри них хранится. Помните наш мультфильм про Винни-пуха? «В нем можно держать все что угодно» — говорил Винни-пух про горшочек из-под меда. Аналогично, рассматриваемый паттерн позволяет в одном и том же объекте хранить значения любого типа. «Variant» это горшочек, в котором можно держать все что угодно.

    Так все же, «Variant» или «Any»?

    В разных языках этот механизм назывался и называется по-разному, при этом доля тех создателей языка, кто в своем языке назвал этот контейнер так-то только потому, что в языках, которые знал создатель, он назывался так же, довольно велика.

    По скромному мнению автора, наиболее верно подобраны имена в библиотеке boost. boost::variant это объект-контейнер, который умеет хранить объекты из заранее определенного набора типов. boost::any это объект-контейнер, в который можно положить все что угодно, без каких-либо ограничений.

    Рассмотрим одну из возможных реализаций паттерна Variant. Данный паттерн можно реализовать большим количеством способов. Я придумал такой (хотя, с точки зрения буста, это все же any :-)

    #include <memory>
    #include <string>
    
    class variant
    {
    public:
    
        template <class T>
        variant& operator = (T const& t)
        {
            typedef type<T> assign_type;
            object = std::auto_ptr<assign_type>(new assign_type(t));
            return *this;
        }
    
        template <class T>
        operator T ()
        {
            typedef type<T> assign_type;
            assign_type& type = dynamic_cast<assign_type&>(*object);
            return type.get();
        }
    
    private:
    
        class base
        {
        public:
    
            virtual ~base() {}
        };
    
        typedef std::auto_ptr<base> base_ptr;
    
        template <class T>
        class type : public base
        {
        public:
    
            type(T const& t)
            : object(t)
            {
            }
    
            T get() const
            {
                return object;
            }
    
        private:
    
            T object;
        };
    
        base_ptr object;
    };
    
    struct dummy
    {
        int a;
        int b;
        int c;
    };
    
    int main()
    {
        variant v1, v2, v3, v4;
    
        v1 = 2;
        v2 = 5.0f;
        v3 = std::string("Pot of gold");
        v4 = dummy();
    
        int         i = v1;
        float       f = v2;
        std::string s = v3;
        dummy       d = v4;
    
        return 0;
    }

    Пример довольно простой, но при этом он хорошо иллюстрирует все достоинства и недостатки данного паттерна.

    Цель паттерна — предоставить возможность осуществлять единообразную работу с разнотипными данными. Например, эти разнотипные данные можно хранить в одном контейнере. Основной недостаток — перенос контроля типов с compile-time-а на run-time.

    Существуют ситуации, в которых данный паттерн может дать сиюминутное удобство. Однако, чаще всего за такое удобство впоследствии придется заплатить, так как воспользовавшись паттерном «Variant», пользователь освобождает компилятор от контроля типов и перетаскивает эту функцию на собственные плечи. Поэтому, если вдруг вы испытали потребность в этом паттерне, прежде всего задумайтесь, почему это произошло. Скорее всего найдется более безопасное и не менее изящное решение.

    boost::any и boost::variant имеют более навороченную функциональность и, кроме того, более безопасны, чем мой вариант. Однако, и они не решают основную проблему — отсутствие возможности выполнить полноценную проверку типов на этапе компиляции.

Для ответа в этой теме необходимо авторизоваться.