Безопасно ли «выгружать» указатель метода и использовать его с указателем базового класса? - PullRequest
11 голосов
/ 25 ноября 2010

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

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

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

1 Ответ

9 голосов
/ 25 ноября 2010

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

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

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

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

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

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

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

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

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

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

...