使用形状参数

时间:2016-03-06 01:04:28

标签: c++ polymorphism

标题很糟糕,我知道。 这里的问题是我有一个继承自形状类

的2D和3D形状类
class Shape {
    public:
    virtual double area() = 0;//Calculates the area of some kind of shape
    ~Shape() { delete this; }
};

class Shape2D :public Shape {
public:
    virtual double perimeter() = 0;//Calculates the perimeter of some kind of 2D-shape
    ~Shape2D() {};
};

class Shape3D :public Shape {
public:
    virtual double volume() = 0;//Calculates the volume of some kind of 3D-shape, area function will return surface area
    ~Shape3D() {};
};

决定所有形状都默认有一个区域。在2D形状中,它具有虚拟周边方法以及来自Shape的区域。在3D形状中,它具有体积,并且虚拟区域方法将被视为表面区域。

我所采用的方式是在菜单中可以选择2d或3d形状:在第2个菜单中,我发起:

Shape2D * s = nullptr;

在3d菜单中,我将发起:

Shape3D * s = nullptr;

然后显示任何信息,我使用方法:

void displayShape2D(Shape2D *)

void displayShape3D(Shape3D *)

然而,我想要的方式是声明:

Shape *s = nullputr;

在main的开头,然后是用户选择的任何形状,我可以设置:

s= new Triangle()

s = new cube();

初始化有效但当我尝试制作显示方法时,我遇到了问题。我希望能够做到:

displayShape(Shape *s)

当给出2d形状并且在方法中我尝试:

cout <<s->perimeter();

它会说周边成员不在形状类中。 然后问题是试图确定形状是2d还是3d,然后显示2d的面积和周长或3d的表面积和体积。这是可能的,还是在专用菜单中创建形状类型,然后使用专用的显示方法是唯一的出路?

2 个答案:

答案 0 :(得分:1)

而另一个答案&#34;有效&#34;这不是我采取的方法。基本上,您希望根据实例的动态类型执行不同的代码:虚函数的用途。

所以只需在您的display类中添加一个(可能是纯的)虚拟Shape成员函数,并在派生类中相应地实现它。

dynamic_cast方法相反,当您添加更多派生类或派生的类时,这并不会中断,甚至会进一步&#34;

;

最后:

~Shape() {
  delete this;
}

这个析构函数是C ++相当于用枪击枪射击自己的脸。使用堆栈或静态分配的实例,这将导致虚假免费(因为实例从未从堆中分配),分配的堆将导致双重释放(因为析构函数在释放占用的内存之前被调用)实例)。

必须做的是让析构函数变为虚拟。否则,只有Shape *它才能正确地破坏指向的实例!

这就是&#34;显示&#34;功能是&#34;通常&#34;实施,至少AFAIK:

struct Shape {
  virtual void write_to(ostream &) const = 0;
  virtual ~Shape() {}
};
struct Shape2D : public Shape {
  void write_to(ostream & stream) const {
    stream << "<2D, whooo>";
  }
};
struct Shape3D : public Shape {
  void write_to(ostream & stream) const {
    stream << "<I got depth, man!>";
  }
};

ostream & operator<<(ostream & stream, Shape const & shape) {
  shape.write_to(stream);
  return stream;
}

现在可以将任何Shape(当使用指针时,取消引用它)写入任何ostream,C ++&#34; style&#34;:

std::unique_ptr<Shape> awesomeShape = std::make_unique<Shape2D>();
std::cout << "Awesome is: " << *awesomeShape << std::endl;

这里,第一个operator<<(ostream &, Shape &)被调用(它可以用于任何Shape - 类似的东西),它调用虚拟成员函数write_to,它以不同的方式为每个派生类实现(虽然Shape中也可能存在通用实现!)。另请参阅this

当您加深层次结构时,会发生dynamic_cast方法的可能问题:

struct ThickShape2D : public Shape2D {
  // whatever
};

动态类型ThickShape2D的实例也可以是dynamic_castShape2D,因此您需要仔细关注这些if子句的顺序。< / p>

但是,引用Jarra McIntyre的话:

  

我认为值得一提的是,使用虚函数和上述方法(以及任何其他方法)之间的设计权衡是复杂的。上述方法允许在多种类型上运行时调度,并简化了诸如(回到上面)具有多个绘制函数的事情。

我完全是第二个。有访客模式(以及它的非循环变体),可能是命令模式和许多其他事情,如果需要更多信息,可以开始查看。有关(原始)RTTI的使用的广泛讨论,请参阅this question and its answers

作为最后一点:我不确切地知道你正在尝试建模的内容,但是认为继承通常不是最好的方法。如果可能,prefer composition over inheritanceEntity component系统是一件好事。

(上面的段落包含6个链接,只是为了没有人错过任何东西。)

答案 1 :(得分:0)

您要做的是运行时类型调度。一种方法是使用RTTI。这允许你做这样的事情:

Shape *s = ...;

if(Shape3D *s3d = dynamic_cast<Shape3D*>(s)) {
    // s3d is now a pointer to a 3d shape
} else if(Shape2D *s2d = dynamic_cast<Shape2D*>(s)) {
    // s2d is now a pointer to a 2d shape
} else {
    // s is neither a Shape2D or Shape3D
}

这是有效的,因为如果s不能转换为Type *,dynamic_cast会计算为nullptr。因此,如果可以将s强制转换为指定的类型,则if语句条件仅计算为true。