调用派生 class 的重写虚拟方法而不公开它们

Invoke overridden virtual methods of a derived class without exposing them

我想编写一个函数 f,它以特定顺序调用派生 class 的两个重写虚拟方法 op_1op_2,无需公开那些超出 f 的方法和包含 classes.

的方法

我能想到一些有效或几乎有效的方法,但我也是 c++ 的新手,希望得到一些帮助,以在约定性、可维护性等方面选择“最佳”方法:

  1. 使 f 成为基础 class (Base) 的 public 非虚拟方法,它按顺序调用虚拟操作,并将操作设为私有。
/***** .h *****/
class Base {
  virtual void op_1() const = 0;
  virtual void op_2() const = 0;
  
public:
  void f();
};

/***** .cpp *****/
void Base::f() {
  op_1();
  op_2();
}
  1. 制作 ops public 并公开一个非成员非好友助手以按顺序调用它们。
/***** .h *****/
struct Base {
  virtual void op_1() const = 0;
  virtual void op_2() const = 0;
};

void f(const Base& b,) {
  b.op_1();
  b.op_2();
}
  1. 将 ops 保留为私有并公开一个 friend 函数以按顺序调用它们。
/***** .h *****/
class Base {
  virtual void op_1() const = 0;
  virtual void op_2() const = 0;
  friend void f(const Base& b);
};

void f() {
  b.op_1();
  b.op_2();
}

#1 看起来很糟糕,因为派生的 classes 也可以访问 f,但它对他们没有用处或用处,可能会被滥用。

#2 解决了这个问题,但封装较少,因为操作是 public.

#3 似乎解决了这两个问题,但我是 C++ 的新手并且对交友事物持冷漠态度——它似乎混淆了接口,我读过的指南(例如 Effective C++ #23)不鼓励在没有明确的情况下使用它需要。这是一个合适的案例吗?

#4 ???可能有更好的方法我看不到。开导我!

特定于应用程序的上下文,以防您需要“为什么”

我需要在应用程序初始化时向第三方数据存储注册一些数据类型 - 我需要按顺序执行 2 个同步操作:

  1. store.data_type<MyAppDataType>() 将应用特定数据类型的 table 添加到商店。
  2. store.data_type<MyAppDataType>().member<MemberDataType>("member_name") 生成在步骤 1 中注册的特定于应用程序的数据类型,并设置其字段之一以与库的反射 API 一起使用 - 对于在非生产版本中进行调试非常有用。

操作 #2 是可选的,但如果执行它应该在操作 #1 之后发生。

我希望我的应用程序的各个模块[1] 将它们自己的数据注册到商店,以避免出现一个大而混乱的文件,其中应用程序的所有数据都注册在一起。为此,我在每个模块中包含一个 class,它扩展了一个 DataRegistrar 基础 class - 派生的基础力量 classes 为两者提供特定于模块的实现以上操作:

struct DataRegistrar {
  virtual void register_data_types(lib::store& store) const = 0;
  virtual void setup_reflection(lib::store& store) const = 0;
};

在应用程序的其他地方,我可以有一些在应用程序启动时运行的代码,这些代码按顺序为每个模块调用 register_data_typessetup_reflection。以下是在简化问题中映射到上述方法的方法:

  1. (映射到上面的#1)
/***** .h *****/
class DataRegistrar {
  virtual void register_data_types(lib::store& store) const = 0;
  virtual void setup_reflection(lib::store& store) const = 0;
  
public:
  void register_data(lib::store& store);
};

/***** .cpp *****/
void DataRegistrar::register_data(lib::store& store) {
    register_data_types(store);
#if !PROD_BUILD
    setup_reflection(store);
#endif
}
  1. (映射到上面的#2)
/***** .h *****/
struct DataRegistrar {
  virtual void register_data_types(lib::store& store) const = 0;
  virtual void setup_reflection(lib::store& store) const = 0;
};

void register_data(const DataRegistrar& module_registrar, lib::store& store) {
    module_registrar.register_data_types(store);
#if !PROD_BUILD
    module_registrar.setup_reflection(store);
#endif
}
  1. (映射到上面的#3)
/***** .h *****/
class DataRegistrar {
  virtual void register_data_types(lib::store& store) const = 0;
  virtual void setup_reflection(lib::store& store) const = 0;
  friend void register_data(const DataRegistrar& module_registrar, lib::store& store);
};

void register_data(const DataRegistrar& module_registrar, lib::store& store) {
    module_registrar.register_data_types(store);
#if !PROD_BUILD
    module_registrar.setup_reflection(store);
#endif
}

[1] 应用程序 areas/directories/domains 中的“模块”,而不是 c++20 模块

让我们从#3 开始:

friend 破坏了封装,这就是 Scott Meyers 不建议这样做的原因。我也不建议,除非你真的需要它。 eg: CRTP的private construct + friend trick 防止错字

我建议#1,因为所有工作都是由 class 扩展接口 DataRegistrar 完成的。此外,您不希望其他人 fiddle 与 op_1()op_2().

如果使用自由函数,需要暴露op_1()op_2()的实例,增加被滥用的机会。