Переопределить общедоступную виртуальную функцию частной базовой функцией?

Рассмотрим два класса A и B со следующим интерфейсом:

class A {
public:
    virtual void start() {} //default implementation does nothing
};

class B {
public:
    void start() {/*do some stuff*/}
};

а затем третий класс, который наследуется от обоих, A публично, потому что он реализует этот «интерфейс», и B конфиденциально, потому что это детали реализации.

Однако в этой конкретной реализации start() должен содержать только вызов B::start(). Поэтому я подумал, что могу использовать ярлык и сделать следующее:

class C: public A, private B {
public:
    using B::start;
};

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

  • Есть ли способ заставить эту работу работать так, как я предполагал, она могла работать?
  • Почему компилятор принимает этот код как допустимый? Как я вижу, теперь есть две функции start() с точно такой же сигнатурой в C, и все же компилятор, похоже, с этим справляется и вызывает только A::start().

РЕДАКТИРОВАТЬ: несколько уточнений:

  • Цель состоит в том, чтобы манипулировать C объектами с помощью A указателей.
  • В настоящее время я использую простую функцию, которая просто вызывает B::start(), мне было особенно интересно, действительно ли объявление использования может «переопределить» виртуальное, и если нет, то как это позволило сосуществовать обеим функциям.
  • Я мог бы опустить несколько вещей, таких как наследование virtual для простоты.

person JBL    schedule 23.01.2017    source источник
comment
Непонятно, чего вы ожидаете. C c; c.start(); должен позвонить B::start().   -  person zdf    schedule 23.01.2017
comment
Это работает здесь: ideone.com/e71lnB   -  person Rama    schedule 23.01.2017
comment
@Rama Я думаю, это больше о A* a = a-›start(); для вашего образца ideone   -  person grek40    schedule 23.01.2017
comment
@grek40 Спасибо! понял!, сейчас не работает... ideone.com/e71lnB   -  person Rama    schedule 23.01.2017
comment
Вы ищете void C::start() override { B::start(); }?   -  person zdf    schedule 23.01.2017
comment
@ZDF Короче говоря, да, я думаю.   -  person JBL    schedule 23.01.2017
comment
Я действительно должен был быть более конкретным. Я действительно манипулирую C через A*.   -  person JBL    schedule 23.01.2017
comment
В вашем коде нет директивы использования.   -  person curiousguy    schedule 23.01.2017
comment
@curiousguy Хм, теперь, когда вы это сказали, я могу действительно неправильно использовать этот термин. Директивы использования предназначены только для пространства имен, и я должен использовать объявления. Спасибо.   -  person JBL    schedule 23.01.2017


Ответы (1)


Есть ли способ заставить эту работу работать так, как я предполагал, она могла работать?

Вы должны переопределить функцию-член и явно вызвать B::start():

class C: public A, private B {
public:
    void start() override { B::start(); }
};

Почему компилятор принимает этот код как допустимый? Как я вижу, теперь есть две функции start() с точно такой же сигнатурой в C, но компилятор, похоже, с этим справляется и вызывает только A::start().

Вы правы, в C доступны две функции-члена (A::start() и B::start()). А в class C, не переопределяя start() или не делая видимым start() любого из базовых классов, выполнив using ...::start(), вы получите ошибку неоднозначности при попытке вызвать функцию-член с использованием неквалифицированного поиска имен из объекта C.

class A {
public:
    virtual void start() { std::cout << "From A\n"; }
};

class B {
public:
    void start() { std::cout << "From B\n"; }
};

class C: public A, private B {
};

int main(){
    A* a = new C();
    a->start();       //Ok, calls A::start()

    C* c = new C();
    c->start();       //Error, ambiguous         
}

Чтобы исправить это, вам нужно будет использовать полное имя, например:

    C* c = new C();
    c->A::start();       //Ok, calls A::start()

Теперь выполнение using B::start() в class C просто объявляет start() ссылкой на B::start() всякий раз, когда такое имя используется из объекта C.

class A {
public:
    virtual void start() { std::cout << "From A\n"; }
};

class B {
public:
    void start() { std::cout << "From B\n"; }
};

class C: public A, private B {
public:
     using B::start();
};

int main(){
    A* a = new C();
    a->start();       //Ok, calls A::start()

    C* c = new C();
    c->start();       //Ok, calls B::start()
}

using B::start делает функцию void B::start() видимой в C, но не переопределяет ее. Чтобы вызвать все вышеперечисленные неквалифицированные вызовы функций-членов, чтобы вызвать B::start(), вы должны переопределить функцию-член в C и заставить ее вызывать B::start()

class A {
public:
    virtual void start() { std::cout << "From A\n"; }
};

class B {
public:
    void start() { std::cout << "From B\n"; }
};

class C: public A, private B {
public:
    void start() override { B::start(); }
};

int main(){
    A* a = new C();
    a->start();         //Ok, calls C::start() which in turn calls B::start()
                        //    ^^^^^^^^^^^^^^^^ - by virtual dispatch

    C* c = new C();
    c->start();         //Ok, calls C::start() which in turn calls B::start()

}
person WhiZTiM    schedule 23.01.2017
comment
Да, я прибегаю к решению, которое вы упомянули первым, прямо сейчас. Мне было интересно узнать, есть ли способ заставить директиву using переопределить виртуальную функцию. - person JBL; 23.01.2017
comment
@JBL, using не влияет на vtables, это в основном механизм времени компиляции в C++ для работы с именами, и его нельзя использовать для переопределения, с другой стороны, переопределение влияет на указатели функций-членов в классе vtable - person WhiZTiM; 23.01.2017
comment
Да, я понимаю это сейчас. Спасибо за ваши объяснения! - person JBL; 23.01.2017
comment
Я получил тот же результат, когда не использовал ключевое слово overwrite:void start() { B::start(); } - person Steven Lee; 06.03.2021