Можно ли привести указатель к члену-переменной в этом случае? - PullRequest
1 голос
/ 03 января 2011

В последнее время я обновлял / обновлял свои знания C ++, и изучение строгого алиасинга заставило меня немного опасаться приведения указателей одного типа к другому. Я знаю, что следующий пример кода работает на моем компиляторе на практике, но я хочу убедиться, что он соответствует действующим стандартам:

#include <iostream>

using namespace std;

class MyBase {

    public:
    virtual void DoSomething() = 0;

};

class MyDerived1 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #1" << endl;
    }

};

class MyDerived2 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #2" << endl;
    }

};

template <typename Base, typename Member1, typename Member2>
struct Tuple {

    public:
    Base* Get(int i) {
        return &(this->*(lookupTable[i]));
    }

    private:
    Member1 member1;
    Member2 member2;

    static Base Tuple::* const lookupTable[2];

};

template <typename Base, typename Member1, typename Member2>
Base Tuple<Base, Member1, Member2>::* const Tuple<Base, Member1, Member2>::lookupTable[2] = {
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member1),
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member2)
};

int main() {

    Tuple<MyBase, MyDerived1, MyDerived2> tuple;

    tuple.Get(0)->DoSomething();
    tuple.Get(1)->DoSomething();

    return 0;

}

По сути, этот простой кортеж содержит пару элементов, каждый из которых должен быть производным от общего базового класса. Функция Get возвращает Base&#42; члену, который представляет данный индекс.

Ключевая часть, о которой я задаюсь вопросом, это reinterpret_casts. Я знаю, что приведение от Derived Struct::&#42; к Base Struct::&#42; обычно нет-нет, но в этом случае я использую только переменную pointers-to-member для получения указателя к объекту. (Я не пытаюсь скопировать производный объект, как если бы он был базовым, и не помещать базовый объект в память производного объекта.) Это работает так, как задумано в G ++, и я просто хочу быть уверен, что я не будет укушен любыми компиляторами за это.

Ответы [ 4 ]

2 голосов
/ 03 января 2011

Вы не должны использовать reinterpret_cast там.На самом деле вы не должны упоминать использование reinterpret_cast где-нибудь, где ваша цель - переносимость.reinterpret_cast по определению имеет результаты, специфичные для платформы.

Для приведения указателя на базу в указатель производного класса используется dynamic_cast, который вернет NULL, если указанный объект не является производным классом.Если вы абсолютно уверены, что класс правильный, тогда вы можете использовать static_cast.

1 голос
/ 04 января 2011

Использование reinterpret_cast почти никогда не переносимо. Кроме того, только допустимое использование указателя на приведения членов является неявным приведением от Type Derived::* до Type Base::* и осторожным использованием static_cast с Type Base::* до Type Derived::*. Поскольку вы хотите изменить тип элемента, а не тип объекта, содержащего элементы, это не тот из них.

Как насчет размещения крошечных функций в этом массиве вместо указателей на члены? Следующий код протестирован и должен быть полностью переносимым.

#include <iostream>

using namespace std;

class MyBase {

    public:
    virtual void DoSomething() = 0;

};

class MyDerived1 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #1" << endl;
    }

};

class MyDerived2 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #2" << endl;
    }

};

template <typename Base, typename Member1, typename Member2>
struct Tuple {

    public:
    Base* Get(int i) {
        return &(this->*lookupTable[i])();
    }

    private:
    Member1 member1;
    Member2 member2;

    template <typename MemType, MemType Tuple::*member>
    Base& GetMember() { return this->*member; }

    typedef Base& (Tuple::*get_member_func)();
    static const get_member_func lookupTable[2];

};

template <typename Base, typename Member1, typename Member2>
const typename Tuple<Base, Member1, Member2>::get_member_func
Tuple<Base, Member1, Member2>::lookupTable[2] = {
    &Tuple::GetMember<Member1, &Tuple::member1>,
    &Tuple::GetMember<Member2, &Tuple::member2>
};

int main() {

    Tuple<MyBase, MyDerived1, MyDerived2> tuple;

    tuple.Get(0)->DoSomething();
    tuple.Get(1)->DoSomething();

    return 0;

}
0 голосов
/ 03 января 2011

Приведение в стиле C всегда лучше, чем reinterpret_cast.

Если приведение в стиле C работает, независимо от действительной платформы.

Всегда избегайте reinterpret_cast.

EDITED

Я имею в виду, что с помощью reinterpret_cast вы можете указать неправильный адрес памяти, обработчик приведения в стиле C обрабатывать все связанные с платформой проблемы, такие как ABI, выравнивание памяти, размер указателя и т. Д.* EDITED По вдохновению комментаторов, я прочитал ISO / IEC 14882: 2003 раздел 5.2.10 "Reinterpret_cast".

Конечно, мое понимание ограничено, но мне приходит в голову вспомнить, почему яненавидел reinterpret_cast в первую очередь.

Я думаю, reinterpret_cast - это отсутствие или очень ограниченное понимание иерархии наследования.

Если приведенный операнд является указателем экземпляра класса, который имеет сложную иерархию наследования (таких как классы ATL / COM), достаточно одного reinterpret_cast, чтобы убить ваш процесс с непонятными ошибками.

Мы можем использовать приведение в стиле C с vГлубокое знание фактической операции броска позади.Но мы должны действительно знать точные детали, чтобы безопасно использовать reinterpret_cast.

0 голосов
/ 03 января 2011

РЕДАКТИРОВАТЬ: ссылка от стандарта.Если я правильно читаю, поскольку вы не соответствуете ни одному из исключений, то, что вы сделали, не определено, и поэтому может работать или не работать на любом конкретном компиляторе.Не существует никаких исключений для типа связанного члена.

Начиная с 5.2.10 / 9 (reinterpret_cast):

Значение типа указатель на член Xтипа T1 »может быть явно преобразовано в значение типа« указатель на член Y типа T2 », если T1 и T2 оба являются функциональными типами или обоими типами объектов.66) Значение указателя с нулевым элементом (4.11) преобразуется внулевое значение указателя члена типа назначения.Результат этого преобразования не определен, за исключением следующих случаев:

- преобразование значения типа «указатель на функцию-член» в другой указатель на тип функции-члена и обратно в исходный тип возвращает исходный указательв значение элемента.

- преобразование значения типа «указатель на элемент данных X типа T1» в тип «указатель на элемент данных Y типа T2» (где требования выравнивания T2 отсутствуютболее строгие, чем в T1), и возвращение к исходному типу дает исходный указатель на значение элемента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...