Специализация чисто виртуального базового метода в производном классе - PullRequest
0 голосов
/ 02 сентября 2018

Мне было интересно, существует ли общий способ / шаблон, позволяющий производному классу иметь несколько более специализированную версию чисто виртуального метода в данном базовом классе.

class Base {
public:
    Base() = default;
    virtual ~Base() = 0;
    virtual void foo(int bar) = 0;
};
inline Base::~Base() {}

class Derived public Base {
public:
    Derived() = default;
    ~Derived() = default;
    void foo(int bar) override {/*...*/}
};

class SpecializedDerived : public Base {
public:
    SpecializedDerived() = default;
    ~SpecializedDerived() = default;
    void foo(int bar, double additionalParameter) override {/*...*/}
};

Переопределение в классе SpecializedDerived невозможно, поскольку сигнатура метода не соответствует сигнатуре в чисто виртуальном классе Base.

Теперь, есть ли способ достичь описанного дизайна? Есть ли способ реализовать «более специализированные методы», поскольку существует наследование классов, которое позволит вам реализовать «более специализированные классы»?

При наборе текста я считал, что мое желание больше похоже на "Чувак, я просто хочу, чтобы вы предоставили какую-то функцию iterate(.)!" вещь.

Единственная идея, которая пришла мне в голову до сих пор, это что-то вроде

class Base {
public:
    Base() = default;
    virtual ~Base() = 0;
    virtual void foo(int bar) = 0;
};
inline Base::~Base() {}

class SpecializedDerived : public Base {
public:
    SpecializedDerived(double addParam) : additionalParam_(addParam) {}
    ~SpecializedDerived() = default;
    void foo(int bar) override {
        iterate(bar, additionalParam_);
        return;
    }
private:
    double additionalParam_;
    void foo(int bar, double additionalParam) {/*...*/}
};

Где этот внутренний вызов функции фактически избыточен, как вы могли бы просто сделать:

class SpecializedDerived : public Base {
public:
    SpecializedDerived(double addParam) : additionalParam_(addParam) {}
    ~SpecializedDerived() = default;
    void foo(int bar) override {/* code using additionalPara_ */}
private:
    double additionalParam_;
};

Ответы [ 2 ]

0 голосов
/ 02 сентября 2018

Зачем нужна подпись matchnig?

Идея, лежащая в основе полиморфизма и виртуальных функций, заключается в том, что вызывающая сторона не должна знать никаких подробностей о реальном классе объекта, который она использует:

Пример:

Base *my_object = find_dynamically_a_relevant_object (...);              
my_object->foo(10);   // could be a Derived or a SpecializedDerived

std::vector<Base*> container;  
...                   // somehow you populate the container with a mix of 
...                   // Derived AND SpecializedDerived objects 
for (auto x: container)
    x->foo(std::srand());  

Вот почему подпись должна точно соответствовать той, которая определена в базовом классе.

Но можно ли использовать другую подпись?

Теперь вы можете очень хорошо определить перегруженный foo() с совершенно другой сигнатурой при трех условиях:

  • Это будет , а не будет override: это другая функция с тем же именем. * Вы можете вызвать перегруженную функцию foo () с ее дополнительными параметрами, только если вы уверены, что объект имеет правильный тип.
  • Вы должны убедиться, что предоставлено переопределение с соответствующей подписью (потому что это чисто виртуальный). Это может, например, просто вызвать вашу перегрузку, используя некоторые произвольные значения для дополнительных параметров)

Пример:

class SpecializedDerived : public Base {
public:
    SpecializedDerived() = default;
    ~SpecializedDerived() = default;
    void foo(int bar) override { foo(bar, 0.0); }
    void foo(int bar, double additionalParameter)  {cout<<"specialized "<<bar<<" "<<additionalParameter<<endl;}
};

... // elsewhere, for example in main(): 

SpecializedDerived my_obj;  
my_obj.foo(10);  // use the override of the base
my_obj.foo(10, 37.2); // use the overload

// suppose that p is a Base* like in the first example
auto *q = dynamic_cast<SpecializedDerived*>(p); 
if (q)  // dynamic cast makes this nullptr if not the right type
    q->foo(10, 37.2); 
else cout << "Not specialized"<<endl; 

Переопределить поведение в зависимости от некоторых дополнительных данных

Теперь, если вы хотите использовать foo () в строго полиморфном контексте, но при этом иметь некоторые (скрытые) дополнительные параметры, есть несколько возможностей, таких как, например:

  • Вы можете расширить базовую подпись и добавить дополнительный, в основном неиспользуемый параметр со значением по умолчанию. В большинстве случаев это плохая идея: что если новый производный класс идет с еще одним параметром.
  • Вы можете ввести дополнительный параметр перед выполнением вызова (либо на этапе разработки, как вы сами предложили, либо с помощью установщика, чтобы изменить значение, когда вам нужно). Работает в большинстве случаев. Единственный риск - убедиться, что дополнительный параметр был установлен правильно перед вызовом foo()
  • вы можете изменить подпись, чтобы использовать в качестве единственного параметра объект, который содержит все реальные параметры. Это требует некоторых дополнительных затрат, но чрезвычайно гибко с переменными параметрами. Единственное, что тип этого объекта также может быть полиморфным, и в этом случае вы должны быть уверены, что для правильного объекта используется правильный тип параметра. Это кажется мне сверхмощным, но супер-супер-рискованным.
0 голосов
/ 02 сентября 2018

Начните с чтения на Википедии + контравариантности . Идея состоит в том, что дочерний элемент может переопределить функцию с той, которая принимает более широкий интерфейс. Функция должна принимать старый интерфейс, но она также может расширять его. Идея не в том, чтобы сломать ожидания кода, который имеет ссылку / указатель на базовый класс.

Обратите внимание, что в настоящее время C ++ не поддерживает контравариантность, поэтому это только академическая дискуссия.

Переопределяющая функция в вашем коде не принимает вызовы к старому интерфейсу, поэтому она не считается контрвариантностью. Переопределена:

virtual void foo(int bar):

Перекрытие:

void foo(int bar, double additionalParameter) override {/*...*/} };

Правильный способ - следовать принципам контравариантности вручную в C ++. Это более или менее то, что вы пытались сделать. Вам необходимо переопределить:

void foo(int bar) override

И заставить его вызывать функцию с дополнительным параметром. Для пользователя классов это выглядит как контравариантность (с дополнительным параметром по умолчанию).

Примечание: перегрузка виртуальной функции обычной функцией опасна и может вызвать проблемы. Перегрузка функции скрывает функции с похожими именами в родительском элементе. Это может вызвать путаницу и ошибки, если с ними не обращаться осторожно. Ключевое слово override делает проблему менее серьезной, но все же имеет некоторые риски.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...