简体   繁体   中英

Polymorphism (inheritance) and value types

I have a bunch of types, PixelMeasure , PointMeasure , CentimeterMeasure and so on, that represent a value with a unit. I would like them to have

  • value semantics: eg effectively immutable, don't have to worry about memory allocation, and
  • polymorphism: I can return an object of type Measure and can operate on it without knowning what specific kind it is. I would also like to be able to put multiple different Measure s into a container.

It seems these are mutually exclusive in C++. For polymorphism, I need to use pointers or references.

I see two options:

  • Use smart pointers, eg shared_ptr . This gives me the behavior that I want (safe, no raw pointers, but polymorphic dispatch). The downsides are:
    • It is verbose (I could hide it behind a typedef if I really wanted).
    • You have memory allocation going on beneath the hood (but the code is not performance-critical and it is hidden away).
    • The semantics is wierd - a copy of my object ( shared_ptr<PixelMeasure> ) will share the same underlying pointer. I can still pretend it to have value semantics - if I make the interface immutable, it shouldn't matter.
  • I thought briefly about not using inheritance (there is no common base class) and dispatching via templates - but in that case I need to know the exact Measure kind at compile time, and can't put them into containers.
  • I could get rid of the classes altogether and just use one class, with a value and a unit field - but that would be a lot less flexible, and the usage syntax would be worse, so I'd rather avoid that.

Any ideas?

You can use type-erase because as Sean Parent puts it, inheritance is the base class of all evil . He also has a presentation Value Semantics and Concept Based Polymorphism which is probably what you want. It is the same idea behind eg std::function .

Basically, you use sub-type polymorphism through inheritance in an internal class to use everything that maps to a concept polymorphically. Here is an example from 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_;
};

Now, you can put everything into a Greeter object which has a greet method. Other examples are boost::any_iterator or std::function.

You will suffer one memory allocation per Measure value.

You can use a wrapper class with appropriate copy-constructor and a pointer to your Measure as a field. You'll probably need to add clone method to Measure.

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;
};

You can use a variant type for this: it avoids dynamic allocation, but makes polymorphic dispatch more complicated.

See Boost.Variant , and there's hopefully a standard version on the horizon.

Alternatively, you can write a more specific discriminated union, providing a nice specific polymorphic-style interface

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM