4

Давайте рассмотрим два класса 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().

EDIT: Несколько уточнения:

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

Это не ясно, что вы ожидали. 'C c; c.start(); 'должен вызывать' B :: start() '. – ZDF

+0

Он работает здесь: http://ideone.com/e71lnB – Rama

+1

@Rama Я думаю, что это больше о A * a = & c; а-> начать(); для вашего образца ideone – grek40

ответ

5

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

Вы должны переопределить функцию-член и явно вызывать 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(), при попытке вызвать функцию-член с помощью неотображенного namelookup с объекта 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() 

} 
+0

Да, я прибегаю к решению, которое вы упомянули первым, прямо сейчас. Мне было более интересно узнать, есть ли способ, чтобы директива 'use' переопределяла виртуальную функцию. – JBL

+1

@JBL, 'using' не влияет на vtables, это в основном механизм времени компиляции в C++ для работы с именами и не может использоваться для переопределения, с другой стороны, переопределение имеет влияние на указатели функций-членов в классе vtable – WhiZTiM

+0

Да, я понимаю это сейчас. Спасибо за ваши объяснения! – JBL

0

Я думаю, вы можете смутить то, что означает «общественный» и «частный». В контексте наследования C++ это просто означает, что все знает, что «C» является «A», тогда как в отношении типов «B» только другие объекты «C» могут обращаться к родительским методам объекта «C» (методы «B») ,

Вторая часть касается двусмысленности наследования. Безusing B::start; Наследование действительно неоднозначно и не будет компилироваться (при переходе на c.start()), поэтому на самом деле добавление этого оператора в ваш пример не является обязательным (с использованием A или B), если вы не хотите постоянно использовать квалифицированные пути.

Непонятно, вы ожидали, что c()->start(); вызовет метод «A» или «B», но оператор using определит его каким бы то ни было способом. То, как вы пишете свой код, немного некорректно - действительно ли «A» действительно нужно начать?

+0

Afterthought - если вам нужны все объекты, чтобы начать, похоже, вам нужен родитель с виртуальным стартом для A и B (бриллиант!). – kabanus

+0

Была моя первая мысль, сделав B наследовать A или что-то в этом роде. Тогда я подумал, что «добавьте структуру виртуального наследования, чтобы избежать переопределения одного метода?». – grek40

+0

@ grek40 Я согласен, но мы не знаем полный объем кода, так что это может быть логика системы. Если это так, то да, не спасибо! – kabanus

Смежные вопросы