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

Указатель на преобразование членов

Я только что нашел следующие абзацы в стандартном проекте С++ 03, относящемся к указателю на преобразование члена.

4.11/2 Указатель на преобразования членов

rvalue типа "указатель на член B типа cv T", где B - тип класса, может быть преобразован в rvalue типа "указатель на член D типа cv T", где D является (раздел 10) B. Если B является недоступным (статья 11), двусмысленным (10.2) или виртуальным (10.1) базовым классом D, программа, которая требует этого преобразования, плохо сформирована. Результат преобразования относится к тому же члену, что и указатель на член перед преобразованием, но он относится к члену базового класса, как если бы он был членом производного класса. Результат ссылается на член в экземпляре Ds из B. Поскольку результат имеет тип "указатель на член D типа cv T", он может быть разыменован объектом D. Результат будет таким же, как если бы указатель на член B был разыменован субъектом B D. Значение значения указателя нулевого элемента преобразуется в значение указателя нулевого элемента назначения .52)

5.2.9/9 static_cast

rvalue типа "указатель на член D типа cv1 T" может быть преобразован в r-значение типа "указатель на элемент B типа cv2 T", где B - базовый класс (раздел 10) D, если существует действительное стандартное преобразование из "указателя на член B типа T" на "указатель на член D типа T" (4.11), а cv2 является той же самой cv-квалификацией, что и более высокая cv-квалификация, чем, cv1.63) Значение указателя нулевого элемента (4.11) преобразуется в значение указателя нулевого элемента для целевого типа. Если класс B содержит исходный элемент или является базовым или производным классом класса, содержащего исходный элемент, результирующий указатель на элемент указывает на исходный элемент. В противном случае результат приведения undefined. [Примечание: хотя класс B нужен не содержит исходный элемент, динамический тип объекта, на котором разыменован указатель на член, должен содержать исходный элемент; см. 5.5. ]

Итак, вот мой вопрос. Как сказано в 5.2.9/9, указатель на член D может быть преобразован в указатель на член B, если существует допустимое преобразование, описанное в 4.11/2. Означает ли это, что если есть член 'm' of D, который не наследуется от B, указатель на member 'm' не может быть передан типу указателя на член B?

class Base { };
class Derived : public Base 
{
    int a;
};
typedef int Base::* BaseMemPtr;
BaseMemPtr pa = static_cast<BaseMemPtr>(&Derived::a); // invalid, as per 5.2.9/9 ?

В примечании 5.2.9/9 также говорится, что хотя класс B не должен содержать исходный элемент, динамический тип объекта, на котором указатель на элемент разыменован, должен содержать исходный элемент.

Я запутался в формулировке этого параграфа. Является ли код выше действительным?

Я искал сайт и там был аналогичный вопрос, С++ наследование и указатели функций-членов, ответ на который был рассмотрен только в том случае, если преобразование из указателя в член базового класса для указателя на член производного класса.

4b9b3361

Ответ 1

Код, который вы написали, абсолютно корректен. Там нет ничего плохого (кроме того, что Derived::a является закрытым). Он хорошо сформирован и поведение определено (до сих пор). Как указано в цитированной части стандарта, совершенно законно указывать указатели на элементы с использованием явного static_cast, что и есть то, что вы делаете. 5.2.9/9 никогда не говорит, что указанный элемент должен присутствовать в базовом классе.

Кроме того, как вы правильно цитировали из стандарта, наличие фактического члена в объекте требуется позже в момент разыменования указателя, а не в момент инициализации. Это, конечно, зависит от динамического типа объекта, используемого в левой части оператора доступа к члену (->* или .*). Тип известен только во время выполнения и, следовательно, не может быть проверен компилятором.

Это требование включено как простая заметка в 5.2.9/9, но оно подтверждается в более формальной форме в 5.5/4

4 Если динамический тип объекта не содержит члена, к которому указатель ссылается, поведение undefined.

Итак, например, в контексте вашего примера следующие строки кода хорошо сформированы

Base b;
b.*pa; // 1

Derived d;
d.*pa; // 2

Base *pb = &d;
pb->*pa; // 3

Однако при первом разыменовании создается поведение undefined (поскольку объект b не содержит члена), а второй и третий являются совершенно законными.