Неожиданное поведение при использовании наследования

#c

Вопрос:

У меня есть Base класс, который затем наследуется Foo и Foo , в свою очередь, передается Bar .

Вот файл заголовка base.h для моего Base класса:

 #pragma once

class Base {
    public:
        void runExample();

    private:
        virtual void print();
};
 

И осуществление base.cpp :

 #include "base.h"

void Base::runExample() {
    print();
}

void Base::print() {};
 

Вот файл заголовка foo.h для моего Foo класса:

 #pragma once
#include "../base/base.h"

class Foo : public Base {
    public:
        Foo();

    private:
        void print();

        const char* toPrint;
};
 

И осуществление foo.cpp :

 #include "foo.h"

Foo::Foo() {
    toPrint = "Hello Foo";
}

void Foo::print() {
    std::cout << toPrint << std::endl;
}
 

Вот файл заголовка bar.h для моего Bar класса:

 #pragma once
#include "../foo/foo.h"

class Bar : public Foo {
    public:
        Bar();

    private:
        const char* toPrint; // "overrides" toPrint from parent class Foo?
};
 

И осуществление bar.cpp :

 #include "bar.h"

Bar::Bar() {
    toPrint = "Hello Bar";
}
 

И, наконец, моя главная функция:

 #include "../bar/bar.h"
int main() {
    Bar bar = Bar();
    bar.runExample();
}
 

Итак, у меня есть такого рода отношения между классами:

Bar есть а Foo , которое есть а Base .

То, что я ожидал увидеть на выходе, было «Привет, бар», но на самом деле я вижу «Привет, Фу».

Если я «переопределю» print метод, объявленный в Foo in Bar для печати toPrint , то я получу ожидаемый результат, однако это, похоже, нарушает точку наследования; т. Е. Зачем мне нужно переопределять функциональность, когда она уже определена.

Я ожидаю, что при Bar.print() вызове он использует Foos реализацию, но фактическое поле toPrint было «заменено» значением внутри Bar реализации.

Я очень новичок в C , и я родом из Котлина. Простите меня, если это похоже на основы C 101, но я довольно смущен тем, почему это происходит. Может ли кто-нибудь указать мне правильное направление?

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

1. Вы не можете «переопределять» переменные-члены.

Ответ №1:

В C элементы данных не могут быть «переопределены», как вы ожидаете, и ссылка на toPrint них не будет динамически привязана к элементам данных с одинаковыми именами в подклассах.

С

 class Bar : public Foo {
        ...
        const char* toPrint; // "overrides" toPrint from parent class Foo?
};
 

вы вводите переменную-член Bar::toPrint рядом с Foo::toPrint , которая наследуется. Код

 void Foo::print() {
    std::cout << toPrint << std::endl;
}
 

всегда будет придерживаться toPrint -члена в его области действия, Foo::toPrint т. е.

Следующий код иллюстрирует это поведение:

 struct Base {
    const char* toPrint = "Base";
    virtual void print() const { cout << toPrint << std::endl; }
};

struct Derived: public Base {
    const char* toPrint = "Derived";
    
    void printBoth() const { cout << "own:" << toPrint << "; inherited: " << Base::toPrint << std::endl; }
};

int main() {
    
    Derived d;
    
    cout << "call inherited method print:" << std::endl;
    d.print();
    
    cout << "call method printBoth, showing both data members:" << std::endl;
    d.printBoth();
}
 

Выход:

 call inherited method print:
Base
call method printBoth, showing both data members:
own:Derived; inherited: Base
 

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

1. Да, это имеет смысл, но, если я введу toPrint Foo защищенную переменную-член и удалю переопределение toPrint from Bar , то она действительно будет «переопределена» конструктором Bar s

2. Если вы удаляете Bar::toPrint , то вы наследуете элемент данных Foo::toPrint и можете получить к нему доступ toPrint ; вы не переопределяете его, вы просто присваиваете другое значение Foo::toPrint в конструкторе Bar .

3. Ага, я вижу, в этом есть смысл. Я думаю, что я довольно сильно пукаю мозгами, потому что у меня перегрузка информацией из-за изучения нового синтаксиса накладные расходы, связанные с тем, что все это совсем другое/новое по сравнению с Java/Kotlin land. Спасибо за твою помощь, Стефан!

4. вы даже можете сделать оба toPrint варианта общедоступными; компилятор не будет жаловаться, так Foo::toPrint как отличается от Bar::toPrint . Никакого переопределения, никакого столкновения имен в элементах данных. См.Расширенный ответ.

5. Это просто правила 🙂