将非虚拟接口和多级继承结合在一起

Bringing non-virtual interfaces and multi-level inheritance together

本文关键字:继承 结合 在一起 多级 虚拟 接口      更新时间:2023-10-16

非虚接口习惯用法(NVI)是不言自明的:你不写public virtual函数,但public函数调用private virtual实现函数,像这样:

class Object{
    virtual void v_load();
public:
    void load(){ v_load(); }
}

这使得你,基类的作者,可以检查和执行前置和后置条件,或者应用其他函数,这样派生类的作者就不会忘记它们。

现在,当您是派生作者时,您可能想自己编写一个基类-让我们称之为Pawn -扩展load()的功能,因此必须覆盖v_load()。但现在你面临着一个问题:

当你覆盖v_load()时,其他想要从你的类派生的客户端,将总是覆盖该行为,他们不能调用Pawn::v_load(),因为它是private函数,他们也不能调用Pawn::load(),因为它在Object中被定义为{ v_load; },这当然会导致无限循环。此外,要求他们这样做可能会导致错误,因为他们忘记了这个电话。如果我想让他们启用这一点,我必须将v_load()的访问指定为Object中的protected,这似乎是一个丑陋的解决方案,因为它会大大削弱Object的封装。

当然,你仍然可以覆盖v_load()来调用一个新的函数v_pawnLoad(),然后被客户端覆盖,但这似乎非常容易出错,因为很多客户端可能会重载错误的函数。

那么,我如何设计Pawn,使客户端仍然可以覆盖v_load(),同时保持检查先决条件或调用其他函数的能力,并且(如果可能的话)不启用,更不用说要求ObjectPawn的客户端调用基本v_load()实现了?

  • 如果你的意图是允许人们"扩展"而不是"替换"load的行为,那么把你目前在v_load中的代码放在load中,然后在最后调用空的v_load
  • 或者如果你想让人们在"替换"或"扩展"之间选择,你可以把v_load改成protected
  • 如果你只是想让他们取代的行为,你的代码很好,因为它是。

作为奖励,在所有这3个变体中,如果您没有默认行为,您可以通过将v_load设置为纯虚拟来"强制"更改"允许"。

如果您希望将覆盖限制为Pawn子类,请将final关键字添加到Pawn中的v_load中,并使用另一个虚拟函数来允许Pawn的子类自定义其行为。

加入一些CRTP怎么样?

#include <iostream>
class BaseObject
{
private:
  virtual void v_load() = 0;
public:
  void load() { v_load(); }
};
template<typename Derived>
class Object : public BaseObject
{
private:
  virtual void v_load() { static_cast<Derived&>(*this).load(); }
};
class Pawn : public Object<Pawn>
{
public:
  void load() { std::cout << "Pawn::load()" << std::endl; }
};
class BlackPawn : public Pawn
{
private:
  virtual void v_load() {
    std::cout << "BlackPawn::v_load()" << std::endl;
    std::cout << "- "; Pawn::load();
  }
public:
  void load() {
    std::cout << "BlackPawn::load()" << std::endl;
    std::cout << "- "; Pawn::load();
  }
};
class BigBlackPawn : public BlackPawn
{
private:
  virtual void v_load() {
    std::cout << "BigBlackPawn::v_load()" << std::endl;
    std::cout << "- "; BlackPawn::load();
  }
public:
  void load() {
    std::cout << "BigBlackPawn::load()" << std::endl;
    std::cout << "- "; BlackPawn::load();
  }
};
template<typename T>
void load(T& x)
{
  x.load();
}

void vload(BaseObject& x)
{
  x.load();
}
int main()
{
  Pawn p;
  BlackPawn bp;
  BigBlackPawn bbp;
  load(p);
  load(bp);
  load(bbp);
  std::cout << std::endl;
  vload(p);
  vload(bp);
  vload(bbp);
}

ideone的输出