Подтвердить что ты не робот

Объявление общедоступного доступа не влияет на указатели функций-членов?

У меня проблема с объявлениями доступа в 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: это вопрос о языке, а не о дизайне класса.

4b9b3361

Ответ 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::*)(), и стандарт говорит, что это хорошо. Итак, я думаю, что ошибка времени компиляции справедлива.