管理std容器中的抽象类

时间:2017-07-06 12:47:23

标签: c++ c++11

经过大量研究后,我仍然不明白如何使用智能指针处理抽象类集合。

以下是我遇到的错误:

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

通过编译最小代码来复制错误(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;
    }

如果我将queue声明为queue<Shape*>,但我不想处理指针 - 意思*。

编辑,此代码编译。感谢大家。 This article建议的Guillaume Racicot有助于让情况更加清晰。

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

2 个答案:

答案 0 :(得分:6)

容器分散注意力。问题是unique_ptr不可复制;如果是的话,它就不会是唯一的。因此,您可能需要添加对std::move的调用:

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

这意味着与原始代码可能要做的事情不同;它从容器中删除对象。如果那不是您想要的,那么std::move不是正确的答案,可能unique_ptr不是正确的机制。

答案 1 :(得分:3)

您的示例中存在许多问题,而不仅仅是滥用智能指针。首先,最明显的一次是你的s声明:

ShapeManager s();

这声明了一个名为s的函数,该函数返回ShapeManager并且不带参数。

也许您打算声明ShapeManager类型的对象?

ShapeManager s{};

// Or

ShapeManager s;

其次,你误用了智能指针。你有一个独特的指针队列。唯一指针是围绕免费存储分配对象的RAII包装器。这意味着它是一个使用new分配的对象构造的包装器。在你的例子中,你没有这样做。您正在使用具有自动存储的对象构建唯一指针。

指向自动存储分配对象的智能指针是观察者指针:不得拥有,删除或尝试管理有关该对象的任何内容。实际上,观察者指针是一种语言特征而不是库特征。它通常被称为指针

这是使用观察者指针的代码:

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

但是,您可能不想使用自动存储来保存它们。我的猜测是你想在任何地方使用std::unique_ptr。这允许提交给形状管理器的对象比它的范围更长。为此,您需要在免费商店分配对象。最常见的方法是使用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();
}