Безопасно ли повышать значение указателя метода и использовать его с указателем базового класса?

Допустим, у меня есть тип указателя, который может содержать адрес метода базового класса. Могу ли я назначить ему адрес метода подкласса и ожидать, что он будет работать правильно? В моем случае я использую его с указателем базового класса, а динамический тип объекта - это производный класс.

struct B
{
    typedef void (B::*MethodPtr)();
};

struct D: public B
{
    void foo() { cout<<"foo"<<endl; }
};

int main(int argc, char* argv[])
{
    D d;
    B* pb = &d;

    //is the following ok, or undefined behavior?
    B::MethodPtr mp = static_cast<B::MethodPtr>(&D::foo);
    (pb->*mp)();
}

Стандарт говорит об этом, говоря о static_cast:

5.2.9.9 An rvalue of type “pointer to member of D of type cv1 T” can be converted to an rvalue of type “pointer to member of B of type cv2 T”, where B is a base class (clause 10) of D, if a valid standard conversion from “pointer to member of B of type T” to “pointer to member of D of type T” exists (4.11), and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. 63) The null member pointer value (4.11) is converted to the null member pointer value of the destination type. If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the result of the cast is undefined. [Note: although class B need not contain the original member, the dynamic type of the object on which the pointer to member is dereferenced must contain the original member; see 5.5.]

Как всегда, мне очень трудно расшифровать стандарт. Это вроде как говорит, что это нормально, но я не уверен на 100%, действительно ли приведенный выше текст применим к ситуации в моем примере кода.


person Timo    schedule 25.11.2010    source источник
comment
почему вы не можете использовать обычное переопределение виртуальной функции?   -  person YeenFei    schedule 25.11.2010
comment
кодирование немного похоже на прогулку по горной дороге, идти по середине дороги безопаснее, чем идти близко к краю, ваш код кажется близким к краю. :-)   -  person AndersK    schedule 25.11.2010


Ответы (1)


Это действительно так.

Если класс B содержит исходный член,

B не содержит D :: Foo, поэтому нет.

или является базовым [...] класса, содержащего исходный член

B является базой D, так что это верно. Как результат:

результирующий указатель на член указывает на исходный член

В пункте 5.2.9 9 говорится, что вы можете повышать только в том случае, если вы также можете понижать, как указано в § 4.11:

Rvalue типа «указатель на член B типа cv T», где B - тип класса, может быть преобразовано в rvalue типа «указатель на член D типа cv T», где D - производный класс ( пункт 10) B. Если B - недоступный (пункт 11), неоднозначный (10.2) или виртуальный (10.1) базовый класс D, программа, которая требует этого преобразования, плохо сформирована.

Это просто говорит о том, что вы можете понижать, пока B доступен, не является виртуальным и появляется только один раз на диаграмме наследования D.

Опасность, присущая указателям на методы восходящего преобразования, заключается в том, что вы можете вызвать mp для объекта, фактический тип которого - B. Пока блок кода, который имеет дело с D :: *, также имеет дело с D *, вы можете избежать этого.

person outis    schedule 25.11.2010
comment
+1, но я думаю, что последнее предложение немного вводит в заблуждение, потому что наличие D* не гарантирует, что динамический тип указанного объекта на самом деле D (или какой-то тип, производный от D), что является тем свойством, которое вам действительно нужно. - person j_random_hacker; 29.11.2010
comment
Да, я не очень доволен формулировкой, но мне трудно придумать правило, которое было бы достаточно широким, правильным и кратким. Может быть, письмо скорее знакомит, чем занимается? - person outis; 04.12.2010
comment
Как насчет As long as a code block that deals with D::* also deals with a D* which points to an object whose dynamic type is either D or derived from D? Хотя может быть слишком многословным. - person Justin Time - Reinstate Monica; 14.06.2016