Не вызывается виртуальная функция наследуемого внутреннего класса

Имеются 2 класса, второй наследуется от первого. Внутри каждого из них есть по одному вложенному классу, второй также наследуется от первого. Они должны отличаться ТОЛЬКО функцией печати.

#include <iostream>
using namespace std;

class A { // базовый класс
        public:
        class InnerA { // внутренний класс
                public:
                void print() {
                        cout << "Bad! Inner A" << endl;
                }
        };
        InnerA in; // объект внутреннего класса

        virtual void foo() { // функция с одинаковым кодом для классов A и B
                // do something
                in.print();
                // do something else
        }
};

class B : public A { // наследник, отличающийся только методами print()
        public:
        class InnerB : public InnerA { // наследник внутреннего класса, отличающийся методом print()
                public:
                void print() {
                        cout << "Good! Inner B, not A" << endl;
                }
        };
        InnerB in; // объект внутреннего класса другого типа (переопределили?)

};

int main() {
        B b; // объект класса B
        b.foo(); // выводит "Bad! Inner A", вместо "Good! Inner B, not A"
}

typeid(b.in) говорит, что это действительно InnerB

При этом объект класса B ведёт себя так же, как и объект класса A, хотя функция print() должна быть переопределена

P.S. Если просто скопировать функцию foo() в класс B, всё работает как надо. Но дублирование всего кода -- плохое решение.


Ответы (2 шт):

Автор решения: DmitryK

Это потому, что вы вызываете функцию foo() из класса A. Класс A ничего не знает о классе B и вызывает print() из собственного вложенного объекта класса InnerA in;.
Вам нужно ещё перегрузить функцию foo() в классе B. Вы же сделали её виртуальной, но почему-то не переопределили.
И поскольку foo() будет иметь доступ к обоим объектам A::in B::in лучше указывать от какого именно in вы хотите вызвать print(). Хотя в данном примере если написать void foo() override { in.print(); } будет вызван B::in.print();

class A 
{
    public:
        class InnerA 
        {   public:
            void print() { cout << "Bad! Inner A" << endl; }
        };
        
        InnerA in; // объект внутреннего класса

        virtual void foo() { in.print(); }
};

class B : public A 
{
    public:
        class InnerB : public InnerA 
        {
            public:
            void print() { cout << "Good! Inner B, not A" << endl; }
        };
        InnerB in; 
        void foo() override { B::in.print(); }
};

А вообще вы путаете виртуальные функции и вложенность объектов. В общем случае, виртуальная функция вызовется в зависимости от типа объекта.

class A 
{
    public:
        virtual void foo() { cout << "class A" << endl; }
};

class B : public A 
{
    public:
        void foo() override{ cout << "class B" << endl; }
};

main()
{
    A *ptr = new B;
    ptr->foo(); // вызовется B::foo(), хотя указатель типа A
}
→ Ссылка
Автор решения: user7860670

В примере у внутреннего класса нет никаких виртуальных функций. InnerB in; ничего не переопределяет, а просто создает еще одно поле, которое затем никак не используется. Тут имело бы смысл передавать указатель на реализацию InnerA - получилось бы почти Dependency Injection без дублирования кода и объектов в классах A и B, а то и вообще можно было бы избавиться от класса B, так как вся логика в наследнике InnerA:

#include <iostream>
#include <memory>
#include <utility>

class A
{   // базовый класс
    public: class InnerA
    {   // внутренний класс
        public: virtual void print()
        {
            ::std::cout << "Bad! Inner A" << ::std::endl;
        }

        public: virtual ~InnerA() {}
    };

    private: ::std::unique_ptr<InnerA> m_in; // объект внутреннего класса

    public: explicit A(::std::unique_ptr<InnerA> in)
    :   m_in{::std::move(in)}
    {}

    public: void foo()
    {   // функция с одинаковым кодом для классов A и B
        // do something
        m_in->print();
        // do something else
    }
};

class B : public A
{   // наследник, отличающийся только методами print()
    public: class InnerB : public InnerA
    {   // наследник внутреннего класса, отличающийся методом print()
        public: void print() override
        {
            ::std::cout << "Good! Inner B, not A" << ::std::endl;
        }
    };

    public: explicit B()
    :   A{::std::unique_ptr<InnerA>{new InnerB{}}}
    {}
};

int main()
{
    B b; // объект класса B
    b.foo(); // выводит "Good! Inner B, not A"
}

→ Ссылка