Полиморфизм и сокрытие данных: переопределяет ли базовый класс или игнорирует ограничения доступа к производному классу?

#c #oop #inheritance

#c #ооп #наследование

Вопрос:

Пожалуйста, посмотрите на следующий список кода:

 #include <iostream>

using namespace std;

class Base {
public:
    virtual void Message() = 0;
};

class Intermediate : public Base {

};

class Final : public Intermediate {
    void Message() {
        cout << "Hello World!" << endl;
    }
};

int main() {
    Final final;
    /* Wont work (obviously):
    Final* finalPtr = amp;final;
    finalPtr->Message();
    */
    // Works:
    Intermediate* finalPtr = amp;final; // or Base* finalPtr = amp;final;
    finalPtr->Message();

    return 0;
}
  

Обратите внимание на следующее:

  1. В абстрактном базовом классе чисто виртуальная функция message() является общедоступной
  2. Промежуточный класс (также абстрактный) наследуется от базового класса (остается ли функция message() общедоступной чисто виртуальной в Intermediate?)
  3. Конечный класс наследуется от промежуточного класса, а функция message() является закрытой (по умолчанию)
  4. В main создается объект типа Final
  5. Создается промежуточный указатель на конечный объект

Вопрос: Если вы запустите программу, строка finalPtr->Message(); успешно вызывает реализацию функции message() функции Final, хотя она закрыта. Как это происходит? Переопределяет ли базовый класс или игнорирует ограничения доступа к производному классу?

Связанный вопрос: В связи с (2.) Выше, как правильно определить промежуточный класс? Нужно ли повторно объявлять чисто виртуальную функцию message() из базового класса, учитывая, что промежуточный класс не предназначен для предоставления реализации.

ПРИМЕЧАНИЕ: код был протестирован как с помощью Digital Mars Compiler (dmc), так и с помощью Microsoft Visual Studio Compiler (cl) и отлично работает в обоих

Ответ №1:

Как это происходит? Переопределяет ли базовый класс или игнорирует ограничения доступа к производному классу?

При общедоступном наследовании все общедоступные члены базового класса становятся общедоступными членами производного класса. Так что да Message() , это общедоступная функция Intermediate .

Функция вызывается по указателю базового класса ( Intermediate ). функция является общедоступной в базовом классе. Динамическая отправка (т.Е. Фактический вызов функции производного класса) происходит только во время выполнения, поэтому это работает.

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

Если вы вызываете функцию для указателя производного класса, то во время компиляции компилятор обнаруживает, что Message() это объявлено private в Final , и, следовательно, выдает ошибку.


Производный класс при выводе из абстрактного класса ДОЛЖЕН предоставлять определение для ВСЕХ чисто виртуальных функций базового класса, невыполнение этого требования приведет к тому, что производный класс также станет абстрактным классом.

Здесь Intermediate class является абстрактным классом, и до тех пор, пока вам не нужно создавать объекты этого класса, он будет работать нормально. Обратите внимание, что вы можете создать указатель на абстрактный класс.

Комментарии:

1. Спасибо @Als. У меня просто есть комментарий: «При общедоступном наследовании все общедоступные члены базового класса становятся общедоступными членами производного класса. «Если вы посмотрите на прокомментированный код, указатель типа Final не будет вызывать функцию message() . Это означает, что конечный класс считает этот метод закрытым, а его базовый класс — нет. Это часть тайны.

2. @JohnGathogo: обновлено, чтобы ответить на ваш запрос.

3. «Динамическая отправка (т.Е. Фактический вызов функции производного класса) происходит только во время выполнения, следовательно, это работает» — это объясняет, почему это работает

4. «вы можете создать указатель на абстрактный класс». Я бы на это надеялся, иначе virtual функции имели бы гораздо меньшую полезность. 😛 Это основа шаблона «интерфейс». Сейчас я много работаю с абстрактными / чисто виртуальными базами, которые наследуются гетерогенными шаблонными типами и т. Д.

Ответ №2:

В C спецификаторы virtual и access являются взаимоисключающими. Именно по этой причине в C доступ может быть сужен для виртуальных методов, тогда как в C # или Java это невозможно.

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

В вашем прокомментированном коде виртуальная функция с ограниченным доступом вызывается через указатель конечного класса. Отсюда ошибка компиляции.