C ++ шаблон друга странное поведение - PullRequest
6 голосов
/ 11 мая 2011

Я вижу то, что не могу объяснить в следующем коде. В VS6, VS9 и GCC T2 :: foo2 () выдает ошибку: 'bar': не может получить доступ к защищенному члену, объявленному в классе 'C1'. Но если вы удаляете C1 :: bar (), он компилируется и работает правильно, даже если T2 все еще обращается к защищенному C1B: bar (), что, как вы думаете, будет той же проблемой.

Обратите внимание, что в T2 :: foo2 () вы можете преобразовать 'pT1' в 'T1 *', и все в порядке, но это по-прежнему не объясняет, почему разрешено использование C1B :: bar (), но C1: : bar () нет.

template<class S> class T2;

template<class T> class T1
{
    //template<class T> friend class T2;  --> this doesn't compile under VS6
    friend class T2<T>;

    protected:
        virtual void bar() { printf("T1\n"); }
};

template<class S> class T2
{
    public:
        void foo1(T1<S> *pT1) { pT1->bar(); }  // --> ok, makes sense, this works either way
        void foo2(S *pT1) { pT1->bar(); }  // --> this fails to compile if C1::bar() is defined, but works for C1B::foo() ???
};

class C1 : public T1<C1>
{
    protected:
        virtual void bar() { printf("C1\n"); }  // --> comment this out and foo2 will compile
};

class C1B : public  C1
{
    protected:
        virtual void bar() { printf("C1B\n"); }
};

class C2 : public  T2<C1>
{
};

void test(void)
{
    C1B c1b;
    C2 c2;
    c2.foo1(&c1b);
    c2.foo2(&c1b);  // --> fails to compile if C1::bar() exists
}

1 Ответ

9 голосов
/ 11 мая 2011

В вашем примере тип параметра S в foo2 равен C1.Отношения между друзьями существуют между T2<S> и T1<S>.Хотя C1 происходит от T1<C1>, оно не становится другом всех друзей T1<C1>.

В выражении pT1->bar, bar находится в C1 и, какДружба не передается производному классу, она является закрытой.

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

void foo2(S *pT1) { pT1->template T1<S>::bar(); }

Проверка доступа теперь прошла успешно.

Ссылка для этого в стандарте '03 C ++ приведена в 11.4 / 10, где просто говорится:

Дружба не является ни наследственной, ни переходной.

Спасибо Potatoswatter за его комментарий.Используя qualified-id для id-expession, мы отключаем виртуальную диспетчеризацию (5.2.2 / 1):

Если выбранная функция не виртуальная или если id-выражение вВыражение доступа к члену класса является квалифицированным идентификатором, эта функция вызывается.

Мы можем добавить не виртуальный диспетчер, или мы можем сначала преобразовать параметр в базовый тип, а затем сделать вызов:

 void foo2(S *pT1) { static_cast< T1<S>* > (pT1)->bar(); }

Виртуальная отправка теперь происходит по мере необходимости.

...