2016-06-24 2 views
4

У меня есть куча типов, PixelMeasure, PointMeasure, CentimeterMeasure и т. Д., Которые представляют собой значение с единицей. Я бы хотел, чтобы у них былоПолиморфизм (наследование) и типы значений

  • Значение семантика: напр. эффективно не изменяются, не нужно беспокоиться о распределении памяти, и
  • Полиморфизм: я могу вернуть объект типа Measure и работать с ним, не зная, какой он вид. Я также хотел бы разместить несколько разных Measure s в контейнере.

Кажется, что они являются взаимоисключающими в C++. Для полиморфизма мне нужно использовать указатели или ссылки.

Я вижу два варианта:

  • Используйте смарт-указатели, например, shared_ptr. Это дает мне поведение, которое я хочу (безопасный, без сырых указателей, но полиморфная отправка). Нижеследующие стороны:
    • Это многословие (я мог бы спрятать его за typedef, если бы захотел).
    • У вас есть распределение памяти, идущее под капотом (но код не критичен по производительности, и он скрыт).
    • Семантика wierd - копия моего объекта (shared_ptr<PixelMeasure>) будет использовать один и тот же базовый указатель. Я все еще могу притвориться, что у него есть семантика значений - если я сделаю интерфейс неизменным, это не имеет значения.
  • Я кратко подумал о том, что вы не используете наследование (нет общего базового класса) и диспетчеризации через шаблоны - но в этом случае мне нужно знать точный тип измерения во время компиляции и не может помещать их в контейнеры ,
  • Я мог бы полностью избавиться от классов и просто использовать один класс со значением и единичным полем, но это было бы намного менее гибким, а синтаксис использования был бы хуже, поэтому я бы предпочел избежать этого.

Любые идеи?

+0

Как насчет [Пользовательских литералов] (http://en.cppreference.com/w/cpp/language/user_literal)? –

+0

Определенные пользователем литералы дали бы мне хороший способ использовать их в коде, но это 1. только синтаксический сахар, основное представление является независимым и 2. Я редко создаю эти экземпляры в коде. Они анализируются из файлов конфигурации. То, что я делаю, например, преобразует их в пиксели или отображает их как есть. Существуют также более сложные единицы, такие как процент от высоты окна, которые ссылаются на окно, например. – jdm

+0

Оберните указатель на базу? Или еще лучше, есть вариант? – lorro

ответ

0

Вы можете использовать класс-оболочку с соответствующим конструктором-копий и указатель на меру как поле. Вероятно, вам нужно добавить метод «Клон» для измерения.

class MeasureWrapper 
{ 
public: 
    MeasureWrapper(const MeasureWrapper &measureToCopy) 
    { 
     m_measure = measureToCopy.m_measure->Clone(); 
    } 

    MeasureWrapper(Measure *measure) : m_measure(measure) 
    { 
    } 

    ~MeasureWrapper() 
    { 
     delete m_measure; 
    } 

    // Wrap Measure interface here and call m_measure methods...  
private: 
    Measure *m_measure; 
}; 
0

Для этого можно использовать тип варианта: он позволяет избежать динамического распределения, но делает сложнее полиморфную отправку.

См. Boost.Variant, и, надеюсь, стандартная версия на горизонте.

В качестве альтернативы, вы можете написать более конкретный дискриминационный союз, обеспечивая приятный специфический интерфейс полиморфный стиле

7

Вы можете использовать тип стирания, потому что, как Шон Родитель выразилась, inheritance is the base class of all evil. У него также есть презентация Value Semantics and Concept Based Polymorphism, которая, вероятно, вы хотите. Это та же самая идея, например, std::function.

В принципе, вы используете полиморфизм подтипа через наследование во внутреннем классе, чтобы использовать все, что отображает концепцию полиморфно.Вот пример из Type Erasure with Merged Concepts:

class Greeter { 
    public: 
    // Constructor: We can stuff anything into a Greeter costume. 
    template <class T> 
    Greeter(T data) : self_(std::make_shared<Model<T>>(data)) {} 

    // External interface: Just forward the call to the wrapped object. 
    void greet(const std::string &name) const { 
     self_->greet(name); 
    } 

    private: 
    // The abstract base class is hidden under the covers... 
    struct Concept { 
     virtual ~Concept() = default; 
     virtual void greet(const std::string &) const = 0; 
    }; 
    // ... and so are the templates. 
    template <class T> 
    class Model : public Concept { 
     public: 
     Model(T data) : data_(data) {} 
     virtual void greet(const std::string &name) const override { 
      // Forward call to user type. 
      // Requires that T can greet. 
      data_.greet(name); 
     } 

     private: 
     // The user defined Greeter will be stored here. (by value!) 
     T data_; 
    }; 

    // Polymorphic types require dynamic storage. 
    // Here we store our pointer to the Model that holds the users Greeter. 
    std::shared_ptr<const Concept> self_; 
}; 

Теперь, вы можете поместить все в объект Greeter, который имеет метод приветсвие. Другими примерами являются boost :: any_iterator или std :: function.

Вы потеряете одно распределение памяти за значение измерения.

+0

Спасибо, это довольно элегантно, особенно со стороны использования. Это напоминает мне немного стилей. Однако каково преимущество перед использованием умного указателя в качестве обертки (помимо скрытия умного указателя)? Я боюсь, что это может быть слишком «умным», и люди не понимают отношения между «Greeter» и «English» (тогда как 'shared_ptr ' и 'shared_ptr ' являются уродливыми, но понятными). Когда у вас есть несколько понятий, трюк в последней ссылке очень приятен, конечно. – jdm

+0

@jdm Если вы используете простой умный указатель, вам нужно иметь общий базовый класс всего, что вы здесь ввели. С стиранием типа вы можете использовать все, что реализует концепцию, то есть все, что имеет тот же синтаксический интерфейс. В качестве примера, если у вас есть 'std :: function ', вы можете назначить функцию, лямбда-выражение или даже объект Functor. – Jens