关于C++:如何在派生类上实现静态成员?

How to enforce a static member on derived classes?

我有一个基类,Primitive,我从中派生出其他几个类——SpherePlane等。

Primitive通过纯虚拟函数在其子类上强制一些功能,例如intersect()intersect的计算依赖于实例数据,因此将其作为成员方法是有意义的。

我的问题出现在以下方面:我希望每个派生实例都能够识别其类型,比如通过std::string type()成员方法。由于同一类的所有实例都将返回相同的类型,因此使type()成为static方法是有意义的。因为我还希望每个Primitive子类实现这个方法,所以我也希望使它成为一个纯粹的虚拟函数,就像上面的intersect()一样。

然而,C++中不允许使用静态虚拟方法。C++静态虚拟成员?和我们能有一个虚拟静态方法吗?(C++)问类似的问题,但它们不包括对派生类强制执行函数的要求。

有人能帮我吗?


让我们考虑一下。我敢肯定你不仅有两个子案例,所以让我们概括一下。

首先想到的是代码复制、可扩展性和紧密性。让我们进一步讨论一下:

如果您想添加更多的类,您应该在尽可能少的地方更改代码。

因为intersect操作是交换的,所以相交AB的代码应该与相交BA的代码在同一个位置,所以将逻辑保持在类本身是不可能的。

另外,添加一个新的类并不意味着你必须修改现有的类,而是扩展一个委托类(是的,我们将在这里讨论模式)。

我假设这是您当前的结构(或者类似的,可能是intersect的返回类型,但现在不重要):

1
2
3
4
5
6
7
8
9
10
11
12
struct Primitive
{
    virtual void intersect(Primitive* other) = 0;
};
struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
};
struct Plane : Primitive
{
    virtual void intersect(Primitive* other);
};

我们已经决定不希望在PlaneSphere中使用交叉逻辑,因此我们创建了一个新的class

1
2
3
4
5
6
7
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&,...)

记住,添加新类时,我们不想更改现有代码。所以我们不能检查intersect函数中的类型。

我们可以为此(策略模式)创建一个行为类,它的行为因类型而异,然后我们可以进行扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }
};

在我们最初的方法中,我们会:

1
2
3
4
5
6
7
8
struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
    {
        SphereIntersectBehavior intersectBehavior;
        return intersectBehavior.doIntersect(other);
    }
};

更清洁的设计将是实现工厂,以抽象出实际的行为类型:

1
2
3
4
5
6
7
8
struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
    {
        IntersectBehavior*  intersectBehavior = BehaviorFactory::getBehavior(this);
        return intersectBehavior.doIntersect(other);
    }
};

你甚至不需要intersect是虚拟的,因为它只需要为每个类做这个。

如果你按照这个设计

  • 添加新类时无需修改现有代码
  • 将实现放在一个地方
  • 每种新类型仅扩展IntersectBehavior
  • intersect类中为新类型提供实现

我敢打赌这会进一步完善。


由于它们在您提供的链接中讨论的原因,您不能使虚拟成员成为静态的。

关于在派生类上强制函数的要求的问题是通过使抽象基类中的函数纯虚拟来处理的,抽象基类将强制派生类必须实现该函数。


As all instances of the same class will return the same type, it makes sense to make type() a static method.

不,不是。当不需要对象的实例来调用函数时,可以使用静态方法。在本例中,您试图标识对象的类型,因此您确实需要一个实例。

无论如何,所有方法体都由所有对象共享,因此不必担心重复。唯一的例外是当函数是内联的,但是编译器会尽最大努力减少开销,如果开销太大,可能会将其变为非内联的。

P.S.要求类在类层次结构之外标识自己通常是一种糟糕的代码味道。试着找到另一种方法。


您可以让一个非静态虚拟方法调用静态方法(或返回静态字符串),并在每个派生类中适当实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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() <<"
"
;

}

请注意,无论如何,您必须为每个子类实现type()方法,所以您所能做的最好的就是使字符串是静态的。但是,您可以考虑使用enum而不是字符串,避免在代码中进行不必要的字符串比较。

现在,回到问题的基本面,我认为设计有些缺陷。您不能真正拥有对所有类型形状进行操作的常规交集函数,因为交集产生的形状类型差异很大,即使对于同一类型的形状(例如,两个平面可以在一个平面、一条直线上相交,或者根本不相交)。因此,在尝试提供一个通用的解决方案时,您会发现自己在各地执行这种类型检查,并且随着添加的形状的增加,这种检查将无法修复地增长。