Can I have polymorphic containers with value semantics in C++?

后端 未结 9 1356
半阙折子戏
半阙折子戏 2020-12-02 18:35

As a general rule, I prefer using value rather than pointer semantics in C++ (ie using vector instead of vector). Usuall

相关标签:
9条回答
  • 2020-12-02 19:24

    Yes, you can.

    The boost.ptr_container library provides polymorphic value semantic versions of the standard containers. You only have to pass in a pointer to a heap-allocated object, and the container will take ownership and all further operations will provide value semantics , except for reclaiming ownership, which gives you almost all the benefits of value semantics by using a smart pointer.

    0 讨论(0)
  • 2020-12-02 19:25

    While searching for an answer to this problem, I came across both this and a similar question. In the answers to the other question you will find two suggested solutions:

    1. Use std::optional or boost::optional and a visitor pattern. This solution makes it hard to add new types, but easy to add new functionality.
    2. Use a wrapper class similar to what Sean Parent presents in his talk. This solution makes it hard to add new functionality, but easy to add new types.

    The wrapper defines the interface you need for your classes and holds a pointer to one such object. The implementation of the interface is done with free functions.

    Here is an example implementation of this pattern:

    class Shape
    {
    public:
        template<typename T>
        Shape(T t)
            : container(std::make_shared<Model<T>>(std::move(t)))
        {}
    
        friend void draw(const Shape &shape)
        {
            shape.container->drawImpl();
        }
        // add more functions similar to draw() here if you wish
        // remember also to add a wrapper in the Concept and Model below
    
    private:
        struct Concept
        {
            virtual ~Concept() = default;
            virtual void drawImpl() const = 0;
        };
    
        template<typename T>
        struct Model : public Concept
        {
            Model(T x) : m_data(move(x)) { }
            void drawImpl() const override
            {
                draw(m_data);
            }
            T m_data;
        };
    
        std::shared_ptr<const Concept> container;
    };
    

    Different shapes are then implemented as regular structs/classes. You are free to choose if you want to use member functions or free functions (but you will have to update the above implementation to use member functions). I prefer free functions:

    struct Circle
    {
        const double radius = 4.0;
    };
    
    struct Rectangle
    {
        const double width = 2.0;
        const double height = 3.0;
    };
    
    void draw(const Circle &circle)
    {
        cout << "Drew circle with radius " << circle.radius << endl;
    }
    
    void draw(const Rectangle &rectangle)
    {
        cout << "Drew rectangle with width " << rectangle.width << endl;
    }
    

    You can now add both Circle and Rectangle objects to the same std::vector<Shape>:

    int main() {
        std::vector<Shape> shapes;
        shapes.emplace_back(Circle());
        shapes.emplace_back(Rectangle());
        for (const auto &shape : shapes) {
            draw(shape);
        }
        return 0;
    }
    

    The downside of this pattern is that it requires a large amount of boilerplate in the interface, since each function needs to be defined three times. The upside is that you get copy-semantics:

    int main() {
        Shape a = Circle();
        Shape b = Rectangle();
        b = a;
        draw(a);
        draw(b);
        return 0;
    }
    

    This produces:

    Drew rectangle with width 2
    Drew rectangle with width 2
    

    If you are concerned about the shared_ptr, you can replace it with a unique_ptr. However, it will no longer be copyable and you will have to either move all objects or implement copying manually. Sean Parent discusses this in detail in his talk and an implementation is shown in the above mentioned answer.

    0 讨论(0)
  • 2020-12-02 19:25

    Take a look at static_cast and reinterpret_cast
    In C++ Programming Language, 3rd ed, Bjarne Stroustrup describes it on page 130. There's a whole section on this in Chapter 6.
    You can recast your Parent class to Child class. This requires you to know when each one is which. In the book, Dr. Stroustrup talks about different techniques to avoid this situation.

    Do not do this. This negates the polymorphism that you're trying to achieve in the first place!

    0 讨论(0)
提交回复
热议问题