09 February, 2012

Небольшая задачка по C++

Пришла в голову маленькая задачка на тонкости C++. Решил опубликовать.

Первая итерация задачи (простая, на внимательность):
#include <iostream>
class Base 
{
protected:
    virtual void protectedVirtualMethod() = 0;
};
class A: public Base 
{
public:
    A(): Base() 
    {
    }
    
protected:
    void protectedVirtualMethod() 
    {
        std::cout << "A" << std::endl;
    }
};

class B : public Base 
{
public:
    B(): Base() 
    {
        a = new A();
    }
    void callProtected() 
    {
        protectedVirtualMethod();
    }

protected:
    void protectedVirtualMethod() 
    {
        std::cout << "B" << std::endl;
        a->protectedVirtualMethod();
    }

private:
    Base *a;
};

int main()
{
    B b;
    b.callProtected();
    return 0;
}
Скомпилируется ли этот код? Почему? Если скомпилируется, то какой будет вывод?
Ответ (надо выделить): Нет, не скомпилируется, ошибка доступа к a->protectedVirtualMethod(), нельзя вызывать protected методы не из своей иерархии.

Вторая итерация задачи (на чутье):
Изменим Base таким образом
class Base 
{
    friend class B;
protected:
    virtual void protectedVirtualMethod() = 0;
};

Теперь скомпилируется? Почему? Что выведет?
Ответ (надо выделить): Скомпилируется, B теперь друг класса Base и может вызывать любые его методы, Виртуальные методы будут работать как им и положено, выведет: B A.
Третья итерация задачи (закрепляем материал):
Меняем в классе B тип *a на A
class B : public Base 
{
//.......
private:
    A *a;
};

Вопросы те же.
Ответ (надо выделить): Не скомпилируется, по той же причине, что и в первом случае, потому что друзья не наследуются.

4 comments:

  1. Результат третьего варианта не совсем очевиден. Думал, будет идентичен второму, там ведь, по сути, та же реализация класса А вызывается.

    ReplyDelete
    Replies
    1. В этом вся и соль, в третьем случае вызывается объект типа A а не объект типа Base, а для объекта A не объявлен друг

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. "Нет, не скомпилируется, ошибка доступа к a->protectedVirtualMethod(), нельзя вызывать protected методы не из своей иерархии."
    Нет, не поэтому.
    Как компилятор на стадии компиляции определит что Base *a будет ссылаться на объект класса A?
    Не компилируется он потому, что доступ к protected членам базовых классов есть только у объектов текущего класса.

    Пример:

    class A {
    protected:
    void protectedMethod() {};
    };

    class B : public A {
    public:
    void call1() { protectedMethod(); } // Ок
    void call2() { b->protectedMethod(); } // Ок
    void call3() { a->protectedMethod(); } // Не скомпилируется

    private:
    A *a;
    B *b;
    };

    ReplyDelete