我通常熟悉多型性,但我對 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<EventA>
方法 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;
好的,所以只有在簽名完全匹配時才能覆寫方法。
方法四: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 是一個很好的解決方案?
uj5u.com熱心網友回復:
您對方法 1-3 的見解通常是正確的:
方法 1 失敗,因為
ClientBase沒有繼承自IEventHandler<>哪個宣告的虛方法。方法二確實是一個代表團,我認為這很好。虛擬方法已經是一個委托——在幕后,一個 vtable 大致相當于一組函式指標。委派
onError只是又一個間接級別,希望呼叫的東西onError不會被頻繁呼叫,從而使性能損失顯著。方法 3 失敗了,因為根據合同,任何覆寫都
onEvent(const EventBase& e)需要接受 anyEventBase&。
方法4失敗,因為ClientBase<EventA>和ClientBase<EventB>是不共享一個共同的基礎完全不同的型別。模板更像是型別工廠而不是型別——實體化之間沒有關系。
如果您想通過繼承使 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 { /* ... */ }
};
ClientA并且由于模板ClientB的onError原因會有不同的實作,但它們都可以轉換為一個共同的ClientBase型別來存盤在一個向量中。
最后一個意見——如果你需要一個抽象類來獲得你想要的代碼組織,這可能表明你的關注點沒有分開:也許IEventHandler<T>真的應該是兩個介面,或者錯誤處理應該由其他物體擁有。
uj5u.com熱心網友回復:
基本問題是具有一個不需要模板引數的帶有虛方法的類模板。這本身并沒有錯,但它很容易給一個人的生活帶來極大的不便。
您的方法 1 的問題是在繼承圖中有多個源節點具有errorHandler. 這些功能是無關的。這是一個簡化的演示:
struct X { virtual void foo() = 0; };
struct Y { virtual void foo() {} };
struct XY : X, Y {};
XY仍然是抽象的,盡管有 的實作foo,因為其中有兩個不相關的foos,統一它們的唯一方法是覆寫foo inXY。這讓很多人感到驚訝。
這里的最佳實踐(據我所知)是將有問題的函式移動到X, Yand的公共基類XY(如果需要,創建一個)。向上層次結構,而不是向下或橫向。它應該是虛擬繼承的(不是死亡鉆石問題,因為它是一個沒有資料成員的 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) {}
};
現場演示。
請注意,MSVC 編譯器可能會對此發出警告 (C4250)。忽略或沉默它。為方便起見,這里收集了有關此主題的 SO 帖子。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/366261.html
下一篇:函式模板引數推導中的編譯器差異
