Указатель на член функции с частной базой - PullRequest
5 голосов
/ 04 августа 2011

Следующий код выдает ошибку времени компиляции:

'base::print': не может получить доступ к закрытому члену, объявленному в классе 'base_der'

Однако,Я сделал член public в производном классе.Почему это не работает?

#include <iostream>

using namespace std;

class base
{
public:
    int i;
    void print(int i)
    {
        printf("base i\n");
    }
};

class base_der : private base
{
public:
    using base::print;
};

int main()
{
    // This works:
    base_der cls;
    cls.print(10);

    // This doesn't:    
    void (base_der::* print)(int);
    print = &base_der::print; // Compile error here
}

Ответы [ 3 ]

5 голосов
/ 04 августа 2011

Я думаю, что есть несколько взаимодействующих проблем, способствующих ошибке:

  1. типы указателей на члены имеют неинтуитивные характеристики преобразования типов
  2. объявление использования не влияет на тип имени, введенного в область действия
  3. в то время как имя base_der::print доступно, класс base все еще не существует, и в попытке преобразовать указатель на член фактический тип класса в типе указатель на член является частью рассмотрения.

C ++ 03 7.3.3 «Декларация об использовании»

Объявление использования вводит имя в декларативную область, в которой появляется объявление использования. Это имя является синонимом имени некоторой сущности, объявленной в другом месте.

Обратите внимание, что хотя имя заносится в новый «регион», это синоним - тип того, к чему относится имя, одинаков. Итак, я думаю, что в вашем примере имя base_der::print имеет тип void (base::*)(int), а не тип void (base_der::*)(int).

Стандарт C ++ 03 также говорит о преобразованиях между типами указатель на элемент ( 4.11 "Преобразование указателя в элемент" ):

Значение типа "указатель на член B типа cv T", где B является типом класса, может быть преобразовано в значение типа "указатель на член D типа cv T", где D является производный класс (пункт 10) из B. Если B является недоступным (пункт 11), неоднозначным (10.2) или виртуальным (10.1) базовым классом D, программа, которая требует этого преобразования, является неадекватной. Результат преобразования ссылается на тот же элемент, что и указатель на член до преобразования, но ссылается на элемент базового класса, как если бы он был членом производного класса. Результат ссылается на член в экземпляре D из B. Поскольку результат имеет тип «указатель на член D типа cv T», его можно разыменовать с помощью объекта D. Результат такой же, как если бы указатель на член B был разыменован с подобъектом B в D.

Также примечание 7.3.3 / 13 «Объявление использования» (выделение добавлено):

В целях разрешения перегрузки функции, которые вводятся посредством объявления об использовании в производный класс, будут обрабатываться так, как если бы они были членами производного класса. В частности, неявный параметр this должен рассматриваться как указатель на производный класс, а не на базовый класс. Это не влияет на тип функции, и во всех других отношениях функция остается членом базового класса.

Теперь пример кода, который генерирует ошибку:

// This doesn't:    
void (base_der::* print)(int);
print = &base_der::print; // Compile error here

пытается преобразовать «указатель на член D» в «указатель на член B», что является преобразованием в неправильном направлении. Если вы подумаете об этом на мгновение, вы поймете, почему преобразование в этом направлении небезопасно. Переменная типа «указатель на член B» может не использоваться с объектом, который имеет какое-либо отношение к class D - но если вы вызываете функцию с типом «указатель на член D» (что и является void (base_der::* print)(int) is), вполне вероятно, что указатель this будет указывать на объект D.

В любом случае, хотя я думаю, что корень проблемы - это проблема преобразования, я думаю, что вы получаете жалобу на доступность, потому что когда компилятор пытается обработать преобразование, он сначала проверяет доступность base - и хотя имя base_der::print (которое является псевдонимом для base::print) доступно из-за объявления using, класс base все еще не является.

Отказ от ответственности: этот анализ исходит от кого-то, кто имеет небольшой опыт в нюансах типов указатель на элемент. Это область C ++, которая сложна, трудна в использовании, кроме как в самых простых сценариях, и, по-видимому, имеет много проблем с переносимостью (см. Статью Дуга Клагстона, http://www.codeproject.com/KB/cpp/FastDelegate.aspx,, которая достаточно стара для решения этих проблем возможно, уже решены, но я подозреваю, что нет).

И когда вы говорите, что что-то в C ++ является одной из более сложных или менее понятных областей, это говорит о многом.

4 голосов
/ 04 августа 2011

Не могу сказать, что знаю почему (и не могу говорить со спецификацией), но сообщение об ошибке clang может быть поучительным:

error: cannot cast private base class 'base' to 'base_der'

Таким образом, изменение типа функции-члена работает, в clangи gcc не менее:

void (base::* print)(int);
print = &base_der::print; // works!
1 голос
/ 04 августа 2011

Это потому, что

class base_der : private base

Наследование private. Так что base недоступно для base_der. Измените это на public, и оно будет работать.

...