【问题标题】:C++ Polymorphism and templated interfacesC++ 多态性和模板化接口
【发布时间】:2021-12-31 01:03:45
【问题描述】:

我对多态性很熟悉,但对 C++ 尤其是模板还很陌生。我必须使用我无法更改的代码(使用框架、所有事件和模板化事件侦听器)和我控制下的代码(下例中的客户端)来跟踪情况。

#include <string>
#include <iostream>
#include <vector>

class EventBase {

    public:
        virtual std::string getData() const = 0;
  
};

class EventA : public EventBase {
    public:
        std::string getData() const override {
            return "Event A";
        }
};

class EventB : public EventBase {
    public:
        std::string getData() const override {
            return "Event B";
        }

};

template<class T_Event>
class IEventHandler
{
    public:
        virtual void onEvent(const T_Event& e) = 0;
        virtual void onError() = 0; 
};

class ClientBase {
    public:
        virtual void startReceiving() = 0;
        virtual void stopReceiving() {
            std::cout << "ClientBase::stopReceiving" << std::endl;
        }
};


class ClientA : public ClientBase, public IEventHandler<EventA> {
    public:
        void onEvent(const EventA& e) override {
            std::cout << "ClientA::onEvent - e.getData()= " << e.getData() << std::endl;
        };

        void onError() override {
            std::cout << "ClientA::onError" << std::endl;
        };

        void startReceiving() override {
            std::cout << "ClientA::startReceiving" << std::endl;
        };
};

class ClientB : public ClientBase, public IEventHandler<EventB> {
    public:
        void onEvent(const EventB& e) override {
            std::cout << "ClientB::onEvent - e.getData()= " << e.getData() << std::endl;
        };

        void onError() override {
            std::cout << "ClientB::onError" << std::endl;
        };

        void startReceiving() override {
            std::cout << "ClientB::startReceiving" << std::endl;
        }; 
};



int main(int, char**) {
    //User Code
    ClientA ca;
    ClientB cb;

    std::vector<ClientBase*> baseClients;
    baseClients.push_back(&ca);
    baseClients.push_back(&cb);

    for(const auto client : baseClients){
        client->startReceiving();
    }

    //Framework Code
    EventA a;
    EventB b;

    std::vector<IEventHandler<EventA>*> eventHandlersA;
    std::vector<IEventHandler<EventB>*> eventHandlersB;

    eventHandlersA.push_back(&ca);
    eventHandlersA[0]->onError();
    eventHandlersA[0]->onEvent(a);

    eventHandlersB.push_back(&cb);
    eventHandlersB[0]->onError();
    eventHandlersB[0]->onEvent(b);

    //User Code
    for(const auto client : baseClients){
        client->stopReceiving();
    }
}

请看这里:https://onlinegdb.com/2MYQhC2G5

我现在想做的是有一个onError 的通用默认实现。

为此,我至少尝试了四种方法。只有第二个有效。如果这种方法 2 实际上是实现它的方法,那么听取 C++ 专家的意见会很高兴。

方法 1 只需将onError 放入ClientBase 并将其从派生客户端中删除即可。

class ClientBase {
    public:
        virtual void startReceiving() = 0;
        virtual void stopReceiving() {
            std::cout << "ClientBase::stopReceiving" << std::endl;
        }

        virtual void onError(){
            std::cout << "ClientBase::onError" << std::endl;
        }
};


class ClientA : public ClientBase, public IEventHandler<EventA> {
    public:
        void onEvent(const EventA& e) override {
            std::cout << "ClientA::onEvent - e.getData()= " << e.getData() << std::endl;
        };

        void startReceiving() override {
            std::cout << "ClientA::startReceiving" << std::endl;
        };
};

编译时失败

error: variable type 'ClientA' is an abstract class
note: unimplemented pure virtual method 'onError' in 'ClientA'

好的,它是抽象的,因为它没有实现 IEventHandler&lt;EventA&gt; 所需的方法

方法 2 修复ClientA中未实现的方法但调用超类方法实现

class ClientA : public ClientBase, public IEventHandler<EventA> {
    public:
        void onEvent(const EventA& e) override {
            std::cout << "ClientA::onEvent - e.getData()= " << e.getData() << std::endl;
        };

        void onError() override {
            ClientBase::onError();
        };

        void startReceiving() override {
            std::cout << "ClientA::startReceiving" << std::endl;
        };
};

有效,但在幕后我认为其他事情正在发生,然后是最初的预期(可能更多的是委托然后是继承)。

可能会乱用模板?

方法3:从派生客户端中删除IEventHandler

class ClientBase : public IEventHandler<EventBase> {
    public:
        virtual void startReceiving() = 0;
        virtual void stopReceiving() {
            std::cout << "ClientBase::stopReceiving" << std::endl;
        }

        virtual void onError(){
            std::cout << "ClientBase::onError" << std::endl;
        }

        virtual void onEvent(const EventBase& e) = 0;
};


class ClientA : public ClientBase {
    public:
        void onEvent(const EventA& e) override {
            std::cout << "ClientA::onEvent - e.getData()= " << e.getData() << std::endl;
        };

        void startReceiving() override {
            std::cout << "ClientA::startReceiving" << std::endl;
        };
};

构建系统讨厌我:

error: non-virtual member function marked 'override' hides virtual member function
note: hidden overloaded virtual function 'ClientBase::onEvent' declared here: type mismatch at 1st parameter ('const EventBase &' vs 'const EventA &')
error: variable type 'ClientA' is an abstract class
note: unimplemented pure virtual method 'onEvent' in 'ClientA' - virtual void onEvent(const EventBase& e) = 0;

好的,只有当签名完全匹配时,您才能覆盖方法。

方法 4:将ClientBase 模板化

template<class T_Event>
class ClientBase {
    public:
        virtual void startReceiving() = 0;
        virtual void stopReceiving() {
            std::cout << "ClientBase::stopReceiving" << std::endl;
        }

        virtual void onError(){
            std::cout << "ClientBase::onError" << std::endl;
        }

        virtual void onEvent(const T_Event& e) = 0;
};


class ClientA : public ClientBase<EventA> {
    public:
        void onEvent(const EventA& e) override {
            std::cout << "ClientA::onEvent - e.getData()= " << e.getData() << std::endl;
        };

        void startReceiving() override {
            std::cout << "ClientA::startReceiving" << std::endl;
        };
};

再次,没有成功。这一次我跟踪客户的结构会崩溃:

std::vector<ClientBase*> baseClients; ----> error: use of class template 'ClientBase' requires template arguments
eventHandlersA.push_back(&ca); ---> error: no matching member function for call to 'push_back'

对于如何实现最初的目标,您还有什么想法吗?还是坚持方法 2 是一个好的解决方案?

【问题讨论】:

    标签: c++ templates polymorphism


    【解决方案1】:

    基本问题是具有不需要模板参数的虚拟方法的类模板。这本身并没有错,但它很容易让一个人的生活变得非常不便。

    您的方法 1 的问题是在具有 errorHandler 的继承图中有多个 source 节点。这些函数不相关。这是一个简化的演示:

    struct X { virtual void foo() = 0; };
    struct Y { virtual void foo() {} };
    struct XY : X, Y {};
    

    XY 仍然是抽象的,尽管实现了foo,因为其中有两个不相关的foo,统一它们的唯一方法是覆盖foo XY。这让很多人感到惊讶。

    这里的最佳实践(据我所知)是将有问题的函数移动到XYXY 的公共基类(如果需要,创建一个)。向上层级,而不是向下或横向。它应该是虚拟继承的(不是死亡钻石问题,因为它是一个没有数据成员的 ABC)。

    所以不要这样做:

    template<class T_Event>
    class IEventHandler
    {
        public:
            virtual void onEvent(const T_Event& e) = 0;
            virtual void onError() = 0; 
    };
    

    改为这样做:

    class IErrorHandler {
       public:
          virtual void onError() = 0; 
              // or whatever default implementation you want
    };
    
    template<class T_Event>
    class IEventHandler : public virtual /* XXX Important! */ IErrorHandler
    {
        public:
            virtual void onEvent(const T_Event& e) = 0;
    };
    
    class ClientBase : public virtual IErrorHandler {
       virtual void onError() override {} // whatever
    };
    
    class ClientA : public ClientBase, public IEventHandler<EventA> {
            virtual void onEvent(const EventA& e) {}
    };
    

    Live Demo.

    注意,MSVC 编译器可能会对此发出警告 (C4250)。忽略或沉默它。为了您的方便,这里是a collection of SO posts on this topic

    【讨论】:

      【解决方案2】:

      您对方法 1-3 的见解通常是正确的:

      • 方法 1 失败,因为 ClientBase 没有从声明虚拟方法的 IEventHandler&lt;&gt; 继承。

      • 方法 2 确实是一个委托,我认为这 很好。虚拟方法已经是一种委托——在底层,一个 vtable 大致相当于一组函数指针。委派onError 只是增加了一层间接性,希望onError 调用的频率不足以显着降低性能。

      • 方法 3 失败,因为根据合同,覆盖 onEvent(const EventBase&amp; e) 的任何内容都需要接受任何 EventBase&amp;

      方法 4 失败了,因为 ClientBase&lt;EventA&gt;ClientBase&lt;EventB&gt; 是完全不同的类型,没有共同的基础。模板更像是类型工厂而不是类型——实例化之间没有关系。

      如果您想让 this 与继承一起工作,您可以通过使用非模板 ClientBase 和介于两者之间的模板层来实现 onError 来明确说明该公共基础:

      template <typename TEvent>
      class ErrorHandlingClient : public ClientBase, public IEventHandler<TEvent> {
          public:
              virtual void onError() override { /* ... */ }
      };
      
      class ClientA : public ErrorHandlingClient<EventA> {
          public:
              void onEvent(const EventA& e) override { /* ... */ }
              void startReceiving() override { /* ... */ }
      };
      
      class ClientB : public ErrorHandlingClient<EventB> { 
          public:
              void onEvent(const EventB& e) override { /* ... */ }
              void startReceiving() override { /* ... */ }
      };
      

      ClientAClientB 由于模板的原因会有不同的 onError 实现,但它们都可以转换为通用的 ClientBase 类型以存储在向量中。

      最后一个意见 - 如果你需要一个抽象类来获得你想要的代码组织,这可能表明你的关注点没有分开:也许IEventHandler&lt;T&gt; 真的应该是两个接口,或者应该拥有错误处理由其他实体。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-06-25
        • 2011-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-15
        • 2016-07-23
        相关资源
        最近更新 更多