Предположим, что у меня есть иерархия нескольких классов:
class A {
public:
virtual void DoStuff() = 0;
};
class B : public A {
public:
// Does some work
void DoStuff() override;
};
class C : public B {
public:
// Calls B::DoStuff and does other work
void DoStuff() override;
};
Это может быть наивно реализовано:
void Derived::DoStuff() {
Base::DoStuff();
...
}
Я полагаю, что эта реализация имеет серьезную проблему: всегда естьНе забывайте вызывать базовую реализацию при переопределениях.
Альтернатива:
class A {
public:
void DoStuff() {
for (auto& func: callbacks_) {
func(this);
}
}
virtual ~A() = default;
protected:
template <class T>
void AddDoStuff(T&& func) {
callbacks_.emplace_back(std::forward<T>(func));
}
private:
template <class... Args>
using CallbackHolder = std::vector<std::function<void(Args...)>>;
CallbackHolder<A*> callbacks_;
};
Использование:
class Derived : public Base {
public:
Derived() {
AddDoStuff([](A* this_ptr){
static_cast<Derived*>(this_ptr)->DoStuffImpl();
});
}
private:
void DoStuffImpl();
};
Однако я считаю, что при использовании этого метода возникают значительные накладные расходы.фактически вызывает DoStuff()
, по сравнению с первой реализацией.В тех случаях использования, которые я видел, возможно, долгое создание объектов не является проблемой (можно также попытаться реализовать что-то вроде «короткой векторной оптимизации», если он этого хочет).
Кроме того, я считаю, что 3 определения длякаждый DoStuff
метод - это слишком много.
Я знаю, что его можно очень эффективно решить, используя шаблон наследования, симулирующий CRTP, и можно скрыть решение на основе шаблона за интерфейсным классом (A
в примере), но я продолжаю задаваться вопросом - разве не должно быть более простого решения?
Меня интересует хорошая реализация call DERIVED implementation FROM BASE, if and only if derived class exists and it has an overriding method
для длинных цепочек наследования (или что-то эквивалентное).
Спасибо!
Редактировать: Мне известна идея, описанная в ответе @ Jarod42, и я не нахожу ее подходящей, потому что считаю, что она уродлива для длинных цепочек наследования - каждый имеетиспользовать разные имена методов для каждого уровня иерархии.