Если имя функции объявлено как «виртуальное», можно ли его снова сделать не виртуальным? - PullRequest
3 голосов
/ 29 июля 2011

Итак, допустим, у меня есть цепочка классов, где каждый класс является производным от класса перед ним.По какой-то причине им всем нравится использовать одно и то же имя для некоторой функции-члена.Вроде как:

class C1 { public:             void f() { cout<<"C1"; }; };
class C2 : public C1 { public: void f() { cout<<"C2"; }; };
class C3 : public C2 { public: void f() { cout<<"C3"; }; };

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

C1 c1; c1.f(); // prints C1
C2 c2; c2.f(); // prints C2
C3 c3; c3.f(); // prints C3

Теперь, если я объявлю некоторые указатели на объекты, а затем вызову функцию f из них, все вызовут функцию, связанную с их соответствующим типом указателя:

C1* p1 = &c1; p1->f(); // prints C1
C1* p2 = &c2; p2->f(); // prints C1
C1* p3 = &c3; p3->f(); // prints C1
C2* p4 = &c2; p4->f(); // prints C2
C2* p5 = &c3; p5->f(); // prints C2
C3* p6 = &c3; p6->f(); // prints C3

Все это супер.Я либо вызываю функцию, связанную с типом объекта, либо вызываю функцию, связанную с типом указателя ...

Или, конечно, я могу сделать функцию «виртуальной».Тогда, если я вызову функцию из какого-либо объекта, я не получу никаких изменений в поведении;однако, если я вызову функцию из некоторого указателя, то я не просто вызову функцию для типа указателя, я на самом деле вызову функцию для типа объекта, на который указывает указатель.Пока все хорошо.

Я даже могу перейти на виртуальную середину через цепочку наследования.Допустим, я поставил виртуальный перед функцией f внутри класса C2.Теперь функция сделана виртуальной (т. Е. При вызове из указателей она использует указатель на тип объекта вместо типа указателя для разрешения вызова функции) не только для своего собственного класса, но и для всех будущих классов, которые

Мой вопрос заключается в следующем: после того, как функция была объявлена ​​виртуальной (в какой-то момент в цепочке наследования), она может быть когда-либо возвращена обратно не виртуальной (далее по цепочкенаследование)?

Для пояснения: когда я говорю «вернуться к не виртуальному поведению», я имею в виду, что когда я вызываю функцию из указателя, она будет использовать тип указателя для разрешения вызова функции (а нетип объекта, на который указывает указатель).

Ответы [ 7 ]

2 голосов
/ 29 июля 2011

Как только функция была объявлена ​​виртуальной (в какой-то момент в цепочке наследования), можно ли когда-нибудь вернуть ее обратно в виртуальную (далее по цепочке наследования)?

Нет. После того, как сигнатура функции была сделана virtual где-то вдоль линии наследования, она останется такой: каждая функция с такой же сигнатурой в производном классе также будет virtual.

Один из способов обойти это - использовать (злоупотреблять) шаблон шаблона :

struct Base {
    virtual void doFoo() { bar(); }
    void foo() { doFoo(); }
};

struct Derived1 : public Base {
    virtual void doFoo() { baz(); }  // "overrides" foo via doFoo
};

struct Derived2 : public Base {
    void foo() { quux(); }  // "un-virtualize" foo by decoupling it from doFoo
};

В этом дереве наследования тип указателя будет определять, какой foo вызывается; если это тот из Base, тип объекта, на который указывает указатель, будет определять, какой doFoo вызывается.

2 голосов
/ 29 июля 2011

Простой ответ: Нет.

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

1 голос
/ 29 июля 2011

Как уже говорили все остальные, вы не можете де-виртуализировать функцию, но абсолютно ничто не мешает вам вызывать конкретную версию функции, которая вам требуется, если это допустимо для базового класса:

C1* p1 = &c1; p1->C1::f(); // prints C1
C1* p2 = &c2; p2->C1::f(); // prints C1, not C2
C1* p3 = &c3; p3->C1::f(); // prints C1, not C3
C2* p4 = &c2; p4->C2::f(); // prints C2
C2* p5 = &c3; p5->C2::f(); // prints C2, not C3
C3* p6 = &c3; p6->C3::f(); // prints C3
1 голос
/ 29 июля 2011

Простой ответ: Нет.

После того, как оно станет виртуальным, оно станет виртуальным во всех производных классах (даже если ключевое слово virtual не будет использовано позднее).

По этой причине несколькоболее новые языки вынуждают вас использовать другое ключевое слово, чтобы вы знали, что вы используете избыточный виртуальный метод (как это может быть неочевидно), но ни один из них не позволяет отменять виртуальный метод.

0 голосов
/ 29 июля 2011

Не уверен, он отвечает вам - вы можете использовать атрибут novtable в VC ++.

0 голосов
/ 29 июля 2011

номер

Решение с помощью шаблона:

class C1 { public:  virtual void f() { g(); }; void g() { std::cout<<"C1"; }; };
class C2 : public C1 { public: virtual void f() { std::cout<<"C2"; }; };
class C3 : public C2 { public: virtual void f() { g(); }};

теперь C3 и его потомки имеют поведение C1.

0 голосов
/ 29 июля 2011

нет, его нельзя вернуть.Я никогда не нуждался в этом и не могу представить реальный пример иерархии классов, который нуждается в этом.

также рекомендуется избегать глубокой иерархии, например, Б.Страуструп.

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