【问题标题】:Simulate constructor behaviour for virtual methods模拟虚方法的构造函数行为
【发布时间】:2018-02-09 18:02:58
【问题描述】:

我目前正在使用 C++ 开发一个小型私人项目,我想出了以下结构:

#include <iostream>

class A
{
    std::vector<int> vec;

protected:
    virtual bool onAdd(int toAdd) {
        // should the 'adding' be suppressed?
        // do some A specific checks
        std::cout << "A::onAdd()" << std::endl;
        return false; 
    }

public:
    void add(int i) {
        if(!onAdd(i)) {
            // actual logic
            vec.push_back(i);
        }
    }
};

class B : public A
{
protected:
    bool onAdd(int toAdd) override {
        // do some B specific checks
        std::cout << "B::onAdd()" << std::endl;
        return false;
    }
};

在此示例中,onAdd 基本上是add 的回调,但以更加多态的方式。

当一个类C 继承自B 并且想要覆盖onAdd 时,就会出现实际问题。在这种情况下,调用C::add 时,B 中的实现将被丢弃(即不调用)。所以基本上我想要实现的是一个类似构造函数的行为,我能够在类层次结构的不同位置覆盖相同的方法,并且所有这些都被调用。

我现在的问题是:是否有可能/设计来实现这一目标?不过,我确信它不会像级联构造函数那样简单。

注意:不要过分关注add 示例。问题是关于类似回调的结构,而不是 add 是否有意义。

【问题讨论】:

  • C 可以直接调用B::onAdd
  • 是的,但我想尽可能避免这种情况。
  • 我想负责从C 调用B::onAdd,因为当add 被调用时,这将是错误的行为。也许我需要补充一点,B 是一种框架边界。
  • @SebastianKaupper 好吧,没有办法避免这种情况。您必须知道在覆盖时应该做什么。没有像构造函数那样的自动化。最后看看 Function Template Pattern 是否有助于解决这个问题。
  • “类构造函数”是什么意思?使用int 的构造函数不会自动调用使用int 的父构造函数。

标签: c++ c++11 constructor c++14 virtual


【解决方案1】:

我只会打电话给我的父母 onAdd()

bool C::onAdd(int toAdd) {return my_answer && B::onAdd(toAdd);}

如果您期望其他开发人员从您的基类继承,这可能会有点令人困惑。但是对于小型私有层次结构,它可以完美运行。

我有时会包含一个 using 语句以使其更明确

class C : public B
{
  using parent=B;
  bool onAdd(int toAdd) override {return my_answer && parent::onAdd(toAdd);}
};

【讨论】:

  • 是的,这是显而易见的。反正它似乎并没有好转,我会坚持下去,谢谢。
  • 我认为回调列表中的下一个回调不应该是回调的责任。
  • @ChrisDrew:如果您正在编写一个希望其他人使用的库,这可能会让人感到困惑。回到 OO 流行的那一天,调用你父母的虚拟方法似乎是正常的,正确的行为。 20 年过去了,它并没有像那样成功,但它仍然是一种合法的技术。
【解决方案2】:
struct RunAndDiscard {
  template<class Sig, class...Args>
  void operator()(Sig*const* start, Sig*const* finish, Args&&...args)const{
    if (start==finish) return;
    for (auto* i = start; i != (finish-1); ++i) {
      (*i)(args...);
    }
    (*(finish-1))(std::forward<Args>(args)...);
  }
};
template<class Sig, class Combine=RunAndDiscard>
struct invokers {
  std::vector<Sig*> targets;
  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return Combine{}( targets.data(), targets.data()+targets.size(), std::forward<Args>(args)... );
  }
};

struct AndTogetherResultWithShortCircuit {
  template<class Sig, class...Args>
  bool operator()(Sig*const* start, Sig*const* finish, Args&&...args)const{
    if (start==finish) return true;
    for (auto* i = start; i != (finish-1); ++i) {
      if (!(*i)(args...)) return false;
    }
    return (*(finish-1))(std::forward<Args>(args)...);
  }
};

这会创建每个实例的待办事项表onAdd

创建每类表更难;您需要将表与父类型的表链接起来,这需要每个类的样板。

如果不自己动手,就无法让 C++ 编译器编写实例版本或类版本。

有涉及反射和具体化的 C++20 提案,以及元类提案,它可能涉及自动编写这样的代码(在每个实例和每个类的基础上)。

Here 是正在测试该技术的一个活生生的例子:

struct AndTogetherResultWithShortCircuit {
  template<class Sig, class...Args>
  bool operator()(Sig*const* start, Sig*const* finish, Args&&...args)const{
    if (start==finish) return true;
    for (auto* i = start; i != (finish-1); ++i) {
      if (!(*i)(args...)) return false;
    }
    return (*(finish-1))(std::forward<Args>(args)...);
  }
};
class A {
  std::vector<int> vec;
protected:
  invokers<bool(A*, int), AndTogetherResultWithShortCircuit> onAdd;
public:
  void add(int i) {
    if (!onAdd(this, i)) {
      vec.push_back(i);
    }
  }
};
class B : public A
{
public:
   B() {
     onAdd.targets.push_back([](A* self, int x)->bool{
       // do some B specific checks
       std::cout << "B::onAdd(" << x << ")" << std::endl;
        return x%2;
     });
   }
};
class C : public B
{
public:
   C() {
     onAdd.targets.push_back([](A* self, int x)->bool{
       // do some B specific checks
       std::cout << "C::onAdd(" << x << ")" << std::endl;
       return false;
     });
   }
};

当您想编写自己的 OO 系统时,您可以使用 C++,但 C++ 不会为您编写。

【讨论】:

  • 哇。这是一个巡回演出。 (或者如果涉及很多咖啡,则可以使用 tour de pharma)无论哪种方式,我都觉得这很有趣且有用。
  • 一个有趣的解决方案,但我必须同意@rex。不能解决我的问题,但在其他用例中肯定有用。
【解决方案3】:

如果您想要一个通用的解决方案,也许您可​​以将 CRTP 与可变参数模板一起使用,而不是运行时多态。

this answerthis answer 中汲取灵感:

template<class... OnAdders> class A : private OnAdders... {
    std::vector<int> vec;

   template<class OnAdder>
   bool onAdd(int toAdd){
     return static_cast<OnAdder*>(this)->onAdd(toAdd);
   }

   template<typename FirstOnAdder, typename SecondOnAdder, class... RestOnAdders>
   bool onAdd(int toAdd){
     if (onAdd<FirstOnAdder>(toAdd))
        return true;

     return onAdd<SecondOnAdder, RestOnAdders...>(toAdd);
   }

public:
    void add(int i) {      
        if (onAdd<OnAdders...>(i))
          return;       

        // actual logic
        vec.push_back(i);
    }
};

class B {
public:
    bool onAdd(int toAdd) {
        // do some B specific checks
        std::cout << "B::onAdd()" << std::endl;
        return false;
    }
};

你可以像这样使用:

A<B,C> a;
a.add(42);

Live demo.

【讨论】:

  • 哇。这实际上是一个非常好的解决方案,也与我最初的预期相似。感谢您指出 CRTP,我以前没有听说过。这个想法应该很容易适应我的需要,谢谢。
【解决方案4】:

以下解决方案使用std::function在每个构造函数期间添加每个回调:

#include <iostream>
#include <vector>
#include <functional>

class A
{
    std::vector<int> vec;

protected:
    bool onAdd(int toAdd) 
    {
        // do some A specific checks
        std::cout << "A::onAdd()" << std::endl;
        return true;
    }

    // vector of callback functions. Initialized with A::onAdd() callback as the first entry
    std::vector<std::function<bool(int)>> callbacks{{[this](int toAdd){return onAdd(toAdd); }}};

public:
    void add(int i) 
    {
        for(auto& callback : callbacks) {
            if(!callback(i))
                return;
        }
        // actual logic
        vec.push_back(i);
    }
};

class B : public A
{
public:
    B()
    {
        callbacks.emplace_back([this](int toAdd){return onAdd(toAdd); });
    }
protected:
    bool onAdd(int toAdd)  
    {
        // do some B specific checks
        std::cout << "B::onAdd()" << std::endl;
        return true;
    }
};

class C : public B
{
    public:
    C()
    {
        callbacks.emplace_back([this](int toAdd){return onAdd(toAdd); });
    }
protected:
    bool onAdd(int toAdd)  
    {
        // do some C specific checks
        std::cout << "C::onAdd()" << std::endl;
        // must also call B::onAdd()
        return true;
    }

};

int main()
{
    C c;
    c.add(5);
}

打印:

A::onAdd()
B::onAdd()
C::onAdd()

【讨论】:

  • 所有这些而不是在C::onAdd() 中调用B::onAdd()?哈哈
  • @Slava 我们中的一些人通过感受痛苦而不是仅仅谈论它来了解更多。 :) 显然,经验法则仍然有效;如果你重写,那么新函数(它知道它继承自什么)应该对调用正确的函数并做正确的事情负全部责任。
  • 同意,问题是这不会像 OP 想要的那样工作,因为它需要返回 A::onAdd() &amp;&amp; B::onAdd() &amp;&amp; C::onAdd() 这很容易实现传统方式,但要为每种情况创建特殊的回调链?跨度>
猜你喜欢
  • 2012-09-02
  • 2010-09-09
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
  • 1970-01-01
  • 2012-04-13
  • 1970-01-01
相关资源
最近更新 更多