标题很糟糕,我知道。 这里的问题是我有一个继承自形状类
的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的表面积和体积。这是可能的,还是在专用菜单中创建形状类型,然后使用专用的显示方法是唯一的出路?
答案 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_cast
到Shape2D
,因此您需要仔细关注这些if子句的顺序。< / p>
但是,引用Jarra McIntyre的话:
我认为值得一提的是,使用虚函数和上述方法(以及任何其他方法)之间的设计权衡是复杂的。上述方法允许在多种类型上运行时调度,并简化了诸如(回到上面)具有多个绘制函数的事情。
我完全是第二个。有访客模式(以及它的非循环变体),可能是命令模式和许多其他事情,如果需要更多信息,可以开始查看。有关(原始)RTTI的使用的广泛讨论,请参阅this question and its answers。
作为最后一点:我不确切地知道你正在尝试建模的内容,但是认为继承通常不是最好的方法。如果可能,prefer composition over inheritance。 Entity 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。