【问题标题】:How to automatically call a method or generate code if a subclass derived from a base class?如果子类派生自基类,如何自动调用方法或生成代码?
【发布时间】:2016-12-01 07:18:19
【问题描述】:

我有一些描述能力/行为的类,例如飞行或驾驶等。这些类中的每一个都有一个特定的方法,必须调用它来加载一些数据 - 例如,Flyable 有loadFlyData(),Drivable 有@ 987654322@。对于每个类,方法名称都是唯一的。

我有许多派生类,它们可能继承自这些行为类中的一个或多个。这些派生类中的每一个都有一个名为loadData() 的方法,我们应该在其中调用所有父行为类的方法,例如loadFlyData()loadDriveData() 等。有没有办法使用元编程自动生成这个方法?由于有很多派生类,如果我可以使用元编程生成这些方法,可能会更易于维护......

行为类:(对象类可能具有这些行为中的任何一种,并且必须调用该类的“加载”方法...

class Flyable {
  void loadFlyData() {
  }
};

class Drivable{
  void loadDriveData() {
  }
};

所有对象类都派生自 Object:

class Object {
  virtual void loadData() {
  }
};

派生类:

class FlyingCar : public Object, public Flyable, public Drivable {
    virtual void loadData() override {
        // How to automatically generate code so that the next two lines are called:
        loadFlyData();
        loadDriveData();
  }
};

【问题讨论】:

  • loadFlyData()loadDriveData 可以同名吗?这样它们只能通过范围来区分,Flyable::loadData()Drivable::loadData()?如果是这样,那么一个基于模板的方法来调用每个 T::loadData() 其中 T 是一个基类,应该可以工作。虽然不确定如何以可变参数的形式获取基类列表。
  • 如果所有基础对象对“加载”函数使用相同的名称(例如loadData),则可以创建一个预处理步骤来获取源文件并对其进行修改。使用模板元编程可能是可能的,但它可能会比它的价值更多的工作(并且您当前的解决方案将更少编写,更重要的是更易于阅读和维护)。也可以在运行时创建代码,但这要困难得多,并且会使其可读性和可维护性更低。您现在拥有的只是最好和最简单的解决方案。
  • 是的,我们可以为 loadFlyData 和 loadDriveData 使用相同的名称。对我来说(来自java背景)的困惑是如果它们具有相同的名称(这就是我使用不同名称的原因)如何处理冲突。下面给出的答案是我正在寻找的东西(通过讲故事的人和 krzaq)。我不知道你可以用 C++ 做这些事情!

标签: c++ c++11 metaprogramming


【解决方案1】:

当然可以。但是,您需要采用一些约定,以便代码可以是通用的。 See it live.

#include <iostream>
using namespace std;

struct Flyable{
  int loadConcreteData(){
    cout << "Flyable\n"; return 0;
  }
};

struct Drivable{
  int loadConcreteData(){
     cout << "Drivable\n"; return 0;
  }
};

class Object{
  virtual void loadData(){
  }
};

template<class ...CS>
struct ConcreteLoader : Object, CS... {
    void loadData() override {
        int load[] = {
            this->CS::loadConcreteData()...
        };
    }
};

class FlyingCar : public ConcreteLoader<Flyable,Drivable>{
};

int main() {
    FlyingCar fc;
    fc.loadData();
    return 0;
}

需要提及的变化:

  1. 必须更改每个具体Load 函数的返回类型。这是为了方便扩展参数包时的“数组技巧”。
  2. 所有加载函数的名称都是相同的,也是出于同样的原因。

一旦 c++17 和折叠表达式推出,原因 (1) 可能会过时。

【讨论】:

  • @OlegBogdanov,哦,它不编译?我想活生生的例子是一个谎言
  • @OlegBogdanov 不要让我们猜测,UB 是什么?
  • 在非 void 函数 [Except main()] 中省略 return 语句并使用返回值会产生未定义的行为。对吗?
  • @W.F.您可能想转换为void,否则它可能会因病态情况而中断(其中loadConcreteData 返回一个重载operator, 的类型)
  • @W.F.没有。只是括号内的内置逗号运算符的常规使用。
【解决方案2】:

你可以创建一个免费函数loadXData(),如果你的班级不是X,它将变成一个noop:

namespace detail
{

void loadFlyData(Flyable* ptr) { ptr->loadFlyData(); }
void loadFlyData(...) {}

void loadDriveData(Drivable* ptr) { ptr->loadDriveData(); }
void loadDriveData(...) {}

}

class FlyingCar : public Object, public Flyable, public Drivable{
public:
    virtual void loadData()override{
        //How to automatically generate code so that the next two lines are called:
        detail::loadFlyData(this);
        detail::loadDriveData(this);
    }
};

demo

虽然我认为使用一个通用名称 loadData 并且只为所有可变性父母调用它可能更可取:

template<typename... Policies>
struct ComposedType : Object, Policies...
{
    virtual void loadData() override {
        int arr[] = {
            ((void)Policies::loadData(), 0)...
        };
        (void)arr;
    }
};

using FlyingCar = ComposedType<Drivable, Flyable>;

demo

上面的loadData可以在C++1z中简化:

virtual void loadData() override {
    ((void)Policies::loadData(), ...);
}

demo

【讨论】:

  • ((void)Policies::loadData(), 0)... 并且返回类型不需要修改。我今天学到了一些东西:)
  • @john (void)arr 是为了在 C++17 之前的编译器(你有 [[maybe_unused]])中保持“未使用的变量”警告。
  • @John C++17 之前的技巧是使用依赖于包扩展的整数初始化整数数组arr。但它们也必须是有效整数,这就是为什么我调用 Policies::loadData(),将其结果转换为 void,然后使用逗号运算符丢弃括号表达式的左侧并使用 0 作为初始值设定项。
  • 如果您有 ComposedType : Object, Policies...ComposedType&lt;Object,Drivable, Flyable&gt;;(请注意,我添加了 Object,在我的示例中不存在),那么包将扩展为 ComposedType: Object, Object, Drivable, FlyableObject 是在这里两次。
猜你喜欢
  • 1970-01-01
  • 2017-08-26
  • 2013-04-11
  • 2017-11-29
  • 1970-01-01
  • 2016-01-01
  • 2013-09-23
  • 2014-01-27
  • 1970-01-01
相关资源
最近更新 更多