我有一个基类Primitive
,我可以从中推导出其他几个类 - Sphere
,Plane
等。
Primitive
通过纯虚函数在其子类上强制执行某些功能,例如intersect()
。 intersect
的计算取决于实例数据,因此将它作为成员方法是有意义的。
我的问题出现在以下方面:
我希望每个派生实例都能够通过std::string type()
成员方法识别其类型。由于同一个类的所有实例都将返回相同的类型,因此将type()
作为static
方法是有意义的。由于我还希望每个Primitive
子类都实现此方法,我还想使它成为纯虚函数,如上面的intersect()
。
但是,C ++中不允许使用静态虚方法。 C++ static virtual members? 和 Can we have a virtual static method ? (c++) 问类似的问题,但它们不包括在派生类上强制执行函数的要求。
任何人都可以帮我解决上述问题吗?
答案 0 :(得分:5)
让我们暂时考虑一下。我相信你不仅有2个sublcasses,所以让我们概括一下。
首先想到的是代码重复,可扩展性和贴近性。让我们对这些进行扩展:
如果您想添加更多类,则应尽可能少地更改代码。
由于intersect
操作是可交换,因此交叉A
和B
的代码应与交叉{{1}的代码位于同一位置}和B
,所以将逻辑保留在类本身是不可能的。
另外,添加一个新类并不意味着你必须修改现有的类,而是扩展一个委托类(是的,我们将在这里进入模式)。
这是你当前的结构,我假设(或类似的,可能是A
的返回类型,但现在不重要):
intersect
我们已经决定不要在struct Primitive
{
virtual void intersect(Primitive* other) = 0;
};
struct Sphere : Primitive
{
virtual void intersect(Primitive* other)
};
struct Plane : Primitive
{
virtual void intersect(Primitive* other);
};
或Plane
内部使用交叉逻辑,因此我们创建了一个新的Sphere
:
class
这是您将添加新功能和新逻辑的类。例如,如果您决定添加struct Intersect
{
static void intersect(const Sphere&, const Plane&);
//this again with the parameters inversed, which just takes this
static void intersect(const Sphere&, const Sphere&);
static void intersect(const Plane&, const Plane&);
};
类,则只需添加方法Line
。
请记住,在添加新类时,我们不希望更改现有代码。所以我们无法检查交叉函数内的类型。
我们可以为此创建一个行为类(策略模式),其行为因类型而异,我们可以继续扩展:
intersec(const Line&,...)
在我们原来的方法中,我们有:
struct IntersectBehavior
{
Primitive* object;
virtual void doIntersect(Primitive* other) = 0;
};
struct SphereIntersectBehavior : IntersectBehavior
{
virtual void doIntersect(Primitive* other)
{
//we already know object is a Sphere
Sphere& obj1 = (Sphere&)*object;
if ( dynamic_cast<Sphere*>(other) )
return Intersect::intersect(obj1, (Sphere&) *other);
if ( dynamic_cast<Plane*>(other) )
return Intersect::intersect(obj1, (Plane&) *other);
//finally, if no conditions were met, call intersect on other
return other->intersect(object);
}
};
更清洁的设计是实施工厂,以抽象出行为的实际类型:
struct Sphere : Primitive
{
virtual void intersect(Primitive* other)
{
SphereIntersectBehavior intersectBehavior;
return intersectBehavior.doIntersect(other);
}
};
你甚至不需要struct Sphere : Primitive
{
virtual void intersect(Primitive* other)
{
IntersectBehavior* intersectBehavior = BehaviorFactory::getBehavior(this);
return intersectBehavior.doIntersect(other);
}
};
是虚拟的,因为它只会为每个类做到这一点。
如果您遵循此设计
intersect
IntersectBehavior
类中为新类型提供实现我敢打赌,这可以进一步完善。
答案 1 :(得分:2)
由于他们在您提供的链接中讨论过的原因,您无法使虚拟成员成为静态成员。
关于在派生类上强制执行函数的要求的问题是通过在抽象基类中使函数为pure virtual来处理的,这将强制派生类必须实现该函数。
答案 2 :(得分:1)
由于同一类的所有实例都将返回相同的类型,因此将type()作为静态方法是有意义的。
不,不。当您不需要对象实例来调用该函数时,可以使用静态方法。在这种情况下,您尝试识别对象的类型,因此执行需要一个实例。
无论如何,所有方法体都由所有对象共享,因此无需担心重复。一个例外是当函数是内联的时候,但编译器会尽力减少开销,如果成本太高,可能会把它变成非内联。
P.S。要求类在类层次结构之外标识自己通常是一种糟糕的代码味道。试着找另一种方式。
答案 3 :(得分:0)
您可以使用非静态虚方法调用静态方法(或返回静态字符串),在每个派生类中适当实现。
#include <iostream>
#include <string>
struct IShape {
virtual const std::string& type() const =0;
};
struct Square : virtual public IShape {
virtual const std::string& type() const { return type_; }
static std::string type_;
};
std::string Square::type_("square");
int main() {
IShape* shape = new Square;
std::cout << shape->type() << "\n";
}
请注意,无论如何,您必须为每个子类实现type()
方法,因此您可以做的最好的事情是将字符串设置为静态。但是,您可以考虑使用enum
而不是字符串,避免在代码中进行不必要的字符串比较。
现在,回到问题的基础,我认为设计存在一些缺陷。你不可能真正拥有对各种形状进行操作的一般交叉函数,因为交叉产生的形状类型变化很大,即使是相同类型的形状(两个平面可以在一个平面,一条线或不相交处相交)所有,例如)。因此,在尝试提供一般解决方案时,您会发现自己会在所有地方执行这类类型检查,并且这会增加您添加的形状。