我想创建一个带有将元素添加到包含抽象父类(super class)的子类的列表的函数的类。

我有一个带有多个子类的抽象类“Shape”。在我的示例中,我使用了“圆形”子类,但是可能存在“矩形”,“多边形”或任何其他形状。

ShapeList类的外观(简化)如下:

ShapeList.h文件

#include "Shape.h"

class Node;

class ShapeList
{
public:
ShapeList() : first(0){};

void add( const Shape& s );

private:
Node *first;
};

ShapeList.cpp文件
#include "ShapeList.h"

class Node
{
public:
    friend ShapeList;
    Shape *shapes;
    Node *next;
    Node( Node *n, Shape *s) : next(n), shapes(s){};

};

void ShapeList::add( const Shape& s ){

    first = new Node(first, s);
}

super 类形状(也简化了)
class Shape
{
    public:
        Shape();
        Shape(double x, double y) : posX(x), posY(y){};

    protected:

        virtual const double area() = 0;

        double posX;
        double posY;
};

子类Circle,它是几个子类之一

Circle.h文件
#include <math.h>
#include <Shape.h>

class Circle : public Shape
{
    public:
        Circle();
        Circle( double x, double y, double r) : Shape(x, y), radie(r){};

        const double area();

    private:
    double radie;
};

Circle.cpp文件
#include "Circle.h"

Circle::Circle()
: Shape(0, 0), radie(0)
{}

const double Circle::area(){
    return radie * radie * M_PI;
}

然后,我想做这样的事情:
#include <iostream>
using namespace std;
#include "shapelist.h"
#include "Circle.h"

int main()
{
  ShapeList list;
  list.add( Circle( 5,5, 3)  );

}

所以问题是:

如何将子类作为参数传递给具有父类(super class)参数的函数?可能吗?为每个形状创建添加功能,并且不能选择模板。

我已经测试并在添加功能中进行了很多更改,但是无法正常工作。怎么了?

请注意,我对代码进行了很多次更改,以至于破坏了此处发布的代码中显而易见的内容。你能指出错误吗?

最佳答案

是的,可以将子类作为参数传递给采用父类(super class)参数的函数,但前提是要通过引用或指针传递。如果尝试按值传递,则会遇到object slicing

但这只是故事的一部分。下一个重要的问题是关于所有权。谁拥有Shape(以及该问题的Node)。谁确保删除了它?管理所有权的最简单方法是使用自动存储,然后当它们超出范围时将被自动删除。在这种情况下,您可以考虑main函数持有的Circle对象在函数末尾超出范围的可能性。您可以将此Circle作为Shape引用传递给ShapeList,然后ShapeList不负责删除它。

int main() {
    ShapeList list;
    Circle c(5, 5, 3);
    list.add( c );
}

但是,仔细检查您的代码,似乎您已 promise 拥有ShapeListShape。在这种情况下,您必须担心内存管理。我警告您,很难正确地进行手动内存管理(以下可能是我错了)。如果您必须通过指针I来拥有对象的所有权强烈建议像using smart-pointers一样使用 std::unique_ptr 。但是,假设您确实要执行手动内存管理。

使用引用传递所有权是非常不寻常的,因此我将ShapeList::add更改为采用指针。如果ShapeList不需要修改Shape,则它可以是指向const的指针:
void ShapeList::add( const Shape *s ){
    first = new Node(first, s);
}

然后在主函数中使用Circle创建new:
int main() {
    ShapeList list;
    list.add( new Circle( 10, 10, 4) );
}

现在,ShapeListNode需要担心手动内存管理,因此您需要考虑rule of three (five)。至少您需要正确定义NodeShapeList的析构函数,并防止复制构造/分配。另外,Shape必须具有virtual析构函数才能正确删除它。

为了在没有警告的情况下编译代码,我还必须进行一些其他小的更改,但我将留给您查找:
constexpr double PI = 3.141592653589793238463;

class Shape
{
    public:
        Shape();
        Shape(double x, double y) : posX(x), posY(y){};
        virtual ~Shape() {}  // Important if a pointer-to-shape is being deleted!

        virtual double area() const = 0;

    protected:
        double posX;
        double posY;
};

class Node
{
    public:
        Node *next;
        const Shape *shape;

        Node( Node *n, const Shape *s) : next(n), shape(s){};
        ~Node() { delete next; delete shape; }

        Node(const Node&) = delete;             // Prevent copying to
        Node& operator=(const Node&) = delete;  // avoid double-deletion.

};

class ShapeList
{
    public:
        ShapeList() : first(nullptr) {};
        ~ShapeList() { delete first; }

        ShapeList(const ShapeList&) = delete;             // Prevent copying to
        ShapeList& operator=(const ShapeList&) = delete;  // avoid double-deletion.

        void add( const Shape* s ){ first = new Node(first, s); }
    private:
        Node *first;
};

class Circle : public Shape
{
    public:
        Circle(): Shape(0, 0), radie(0) {}
        Circle( double x, double y, double r) : Shape(x, y), radie(r){};

        double area() const { return radie * radie * PI; }

    private:
        double radie;
};

int main() {
    ShapeList list;
    list.add( new Circle( 10, 10, 4) );
}

如果要保持主函数的外观不变,另一种选择是让Shape具有一种多态复制自身的方法,以避免对象 slice 。通常,这是使用 virtual clone function。然后ShapeList::add可以通过引用进行引用,然后在内部进行“克隆”操作。

当然,实际上,鉴于标准库中已存在std::list,因此您无需编写自己的列表类。将其与unique_ptr is simple放在一起。

09-07 18:12