Объявление общедоступного доступа не влияет на указатели функций-членов?
У меня проблема с объявлениями доступа в g++ (версия 5.1).
class Base
{
public:
void doStuff() {}
};
class Derived : private Base
{
public:
// Using older access declaration (without using) shoots a warning
// and results in the same compilation error
using Base::doStuff;
};
template<class C, typename Func>
void exec(C *c, Func func)
{
(c->*func)();
}
int main()
{
Derived d;
// Until here, everything compiles fine
d.doStuff();
// For some reason, I can't access the function pointer
exec(&d,&Derived::doStuff);
}
g++ не удается скомпилировать вышеуказанный код с помощью:
test.cpp: при создании экземпляра 'void exec (C *, Func) [с C = Derived; Func = void (Base:: *)()]: test.cpp: 24: 27: требуется отсюда
test.cpp: 17: 4: ошибка: "База - это недоступная база" Производные (С → * FUNC)();
Даже когда сама функция может быть вызвана (d.doStuff();
), указатель нельзя использовать, хотя я объявлял функцию доступной извне.
Частное наследование также важно, в какой-то мере, потому что класс Derived
выбирает для отображения только определенный набор элементов из базы (ов), которые являются реализациями интерфейса IRL.
NB: это вопрос о языке, а не о дизайне класса.
Ответы
Ответ 1
Проблема в том, что &Derived::doStuff
на самом деле не является указателем на член класса Derived
. Из [expr.unary.op]:
Результат унарного оператора &
является указателем на его операнд. Операнд должен быть lvalue или identified-id. Если операнд является квалифицированным идентификатором, именящим нестатический или вариантный член m
некоторого класса C
с типом T
, результат имеет тип "указатель на член класса C
типа T
" и является значением prvalue, обозначающим C::m
.
doStuff
не является членом Derived
. Он является членом Base
. Следовательно, он имеет указатель на тип члена Base
или void (Base::*)()
. То, что здесь используется, используется для упрощения разрешения, из [namespace.udecl]:
В целях разрешения перегрузки функции, которые вводятся с помощью объявления-объявления в производный класс будет рассматриваться так, как если бы они были членами производного класса.
Вот почему работает d.doStuff()
. Однако через указатель функции вы пытаетесь вызвать функцию-член Base
для объекта Derived
. Здесь нет разрешения перегрузки, так как вы напрямую используете указатель на функцию, поэтому функция базового класса будет недоступна.
Вы могли бы подумать, что просто введите &Derived::doStuff
в "правильный" тип:
exec(&d, static_cast<void (Derived::*)()>(&Derived::doStuff));
Но вы не можете сделать это либо по [conv.mem], так как снова Base
является недоступной базой Derived
:
Указатель типа "указатель на тип B
типа cv T
", где B
- тип класса, может быть преобразован в prvalue типа "указатель на член D
типа cv T
", где D
является производным классом (раздел 10) B
. Если B
является недоступный (раздел 11), двусмысленный (10.2) или виртуальный (10.1) базовый класс D
или базовый класс виртуальной базы класс D
, программа, которая требует этого преобразования, плохо сформирована.
Ответ 2
Я думаю, причина в том, что функция-член действительно не является частью производного класса, а скорее базового класса. Это можно как-то показать эмпирически, проверяя тип указателя на функцию-член и сравнивая его с указателем на функцию базового элемента:
cout << typeid(&Derived::doStuff).name() << endl
<< typeid(& Base::doStuff).name() << endl;
Живите здесь.
В настоящее время я ищу стандарт для некоторого фона.
Ответ Барри содержит соответствующие части стандарта.
Ответ 3
В соответствии с stardard [namespace.udecl]:
Использование-объявления вводит имя в декларативный регион, в котором появляется декларация использования.
Если декларация using указывает конструктор (3.4.3.1), это неявно объявляет набор конструкторов в классе, в котором появляется декларация использования (12.9); в противном случае имя, указанное в using-declaration - синоним набора объявлений в другом пространство имен или класс.
Итак, вы вводите Base::doStuff
в область Derived
, она все еще является функцией-членом Base
.
Затем exec
создается как exec<Derived, void (Base::*)()>
, но не может отличить от Derived*
до Base*
из-за частного наследования.
Ответ 4
Из стандарта С++ 11, §7.3.3 [namespace.udecl], 18:
class A
{
private:
void f( char );
public:
void f( int );
protected:
void g();
};
class B : public A
{
using A::f; // error: A::f(char) is inaccessible
public:
using A::g;
// B::g is a public synonym for A::g
};
Обратите внимание, что B:: g является общедоступным синонимом для части A:: g. Когда вы берете адрес Derived::doStuff
, GCC создает указатель на функцию-член типа void(Base::*)()
, и стандарт говорит, что это хорошо. Итак, я думаю, что ошибка времени компиляции справедлива.