Managing abstract classes in std container

After a lot of research, I still don't understand how to deal with an abstract collection of classes using smart pointers.

Here are the errors I got:

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Shape; _Dp = std::default_delete<Shape>]'
   base_ptr s = shapes.front();

error: no matching function for call to 'std::unique_ptr<Shape>::unique_ptr(Shape&)'
   shapes.push(base_ptr(b));

      

By compiling the minimal code to replicate the error ( code online avaiable ).

    #include <queue>
    #include <memory>

    class Shape {
    public:
        virtual int getPerimeter() =0;
    };

    typedef std::unique_ptr<Shape> base_ptr;

    class Circle : public Shape {
    public:
        virtual int getPerimeter() { return 1; };
    };

    class Square : public Shape {
    public:
        virtual int getPerimeter() { return 0; };
    };

    class ShapeManager {
    public:
        ShapeManager();
        void useShape() {
            if(shapes.empty())
                throw "Work stack is empty.";

            base_ptr s = shapes.front();
            s->getPerimeter();
            shapes.pop();
        }

        void submitShape(Shape &b) {
            shapes.push(base_ptr(b));
        }
    private:
        std::queue<base_ptr> shapes;
    };

    int main(int argc, char **argv) {
        ShapeManager s();
        Circle c;
        s.submitShape(c);
        s.useShape();
        return 1;
    }

      

It works if I declare queue

as queue<Shape*>

, but I don't want to deal with -meaning * pointers.

EDIT , this code compiles. Thanks to all. This article , suggested by Guillaume Racicot , helps to see a clearer situation.

#include <queue>
#include <memory>

class Shape {
public:
    virtual int getPerimeter() =0;
};

typedef std::unique_ptr<Shape> base_ptr;

class Circle : public Shape {
public:
    Circle() {};
    virtual int getPerimeter() { return 1; };
};

class Square : public Shape {
public:
    virtual int getPerimeter() { return 0; };
};

class ShapeManager {
public:
    ShapeManager();
    void useShape() {
        if(shapes.empty())
            throw "Work stack is empty.";

        base_ptr s = std::move(shapes.front());
        s->getPerimeter();
        shapes.pop();
    }

    void submitShape(base_ptr b) {
        shapes.push(std::move(b));
    }
private:
    std::queue<base_ptr> shapes;
};

int main(int argc, char **argv) {
    ShapeManager s;
    base_ptr c = std::make_unique<Circle>();
    s.submitShape(std::move(c));
    s.useShape();
    return 1;
}

      

+3


source to share


2 answers


The container is a distraction. The problem is that unique_ptr

it cannot be copied; if it were, it would not be unique. So you probably need to add a call std::move

:

base_ptr s = std::move(shapes.front());

      



This means something different from what the source code should have done; it removes the object from the container. If this is not what you wanted, then it is std::move

not the correct answer and may unique_ptr

not be the correct mechanism.

+6


source


Your example has a lot of problems, not just an abuse of smart pointers. First, the most obvious is your declaration s

:

ShapeManager s();

      

This declares a function with a name s

that returns ShapeManager

and takes no parameters.

Perhaps you meant to declare an object of type ShapeManager

?

ShapeManager s{};

// Or

ShapeManager s;

      

Second, you are abusing the smart pointer. You have a unique pointer queue. A unique pointer is an RAII wrapper around an object allocated by free storage. This means that it is a wrapper built with an object selected with new

. In your example, you don't. You are creating a unique pointer with an auto-store object.



A smart pointer pointing to an allocated storage object is an observer pointer: it must not own, delete, or attempt to manipulate anything about that object. In fact, the observer pointer is a language function, not a library function. It is usually called a pointer.

This is your code using observer pointers:

template<typename T>
using observer_ptr = T*;

struct ShapeManager {
    void useShape() {
        if(shapes.empty())
            throw "Work stack is empty.";

        auto s = shapes.front();

        s->getPerimeter();
        shapes.pop();
    }

    void submitShape(Shape &b) {
        shapes.push(&b);
    }

private:
    std::queue<base_ptr> shapes;
};

int main() {
    ShapeManager s;
    Circle c; // Automatic storage
    Rectangle r; // Automatic storage too.

    s.submitShape(c);
    s.submitShape(r);
    s.useShape();
}

      

However, you may not want to hold onto them using automatic storage. I assume you want to use std::unique_ptr

everywhere. This allows the object passed to the form dispatcher to outlive its scope. To do this, you will need to allocate objects in free storage. The most common way is to use std::make_unique

:

struct ShapeManager {
    void useShape() {
        if(shapes.empty())
            throw "Work stack is empty.";

        // We must create a reference,
        // Using simply auto would require copy,
        // Which is prohibited by unique pointers
        auto&& s = shapes.front();

        s->getPerimeter();
        shapes.pop();
    }

    void submitShape(base_ptr b) {
        shapes.push(std::move(b));
    }

private:
    std::queue<base_ptr> shapes;
};

int main() {
    ShapeManager s;

    // Allocated on the free store,
    // The lifetime of c and r are managed by
    // The unique pointer.
    auto c = std::make_unique<Circle>();
    auto r = std::make_unique<Rectangle>();

    s.submitShape(std::move(c));
    s.submitShape(std::move(r));
    s.useShape();
}

      

+3


source







All Articles