Можно ли в этом случае использовать указатель на переменную-член?

В последнее время я освежал/обновлял свои знания 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* элементу, который представляет данный индекс.

Ключевая часть, которая меня интересует, — это reinterpret_casts. Я знаю, что приведение от Derived Struct::* к Base Struct::* обычно недопустимо, но в данном случае я использую переменную указателя на элемент только для получения указателя на объект. (Я не пытаюсь копировать производный объект, как если бы это был базовый объект, и не запихиваю базовый объект в память производного объекта.) Это работает так, как задумано на G++, и я просто хочу быть уверенным, что не ошибусь. будет укушен любым совместимым компилятором за это.


person nonoitall    schedule 03.01.2011    source источник


Ответы (4)


Использование 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;

}
person aschepler    schedule 03.01.2011
comment
У меня тоже была такая мысль, но моя главная причина этой запутанной настройки — попытаться добиться как можно меньшего количества вызовов функций. Я могу сделать это в крайнем случае, но я надеюсь, что кто-то знает (совместимый) способ добиться этого без необходимости вызова функции. - person nonoitall; 04.01.2011

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

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

person Öö Tiib    schedule 03.01.2011
comment
Результат reinterpret_cast по определению не зависит от платформы. Это также не зависит от платформы под каким-либо другим именем. Однако это опасно, потому что нарушает безопасность типов. Результат может быть катастрофическим, а обслуживание кода ненадежным. Но в остальном, да, вместо этого следует использовать dynamic_cast. - person Brent Arias; 03.01.2011
comment
dynamic_cast здесь не работает, потому что эти указатели не указывают на объекты — они указывают на относительные смещения элементов. В момент использования приведения нет RTTI для динамического приведения. static_cast также не будет работать, потому что типы указателей технически несовместимы. - person nonoitall; 03.01.2011
comment
Если вы читали стандарт, то единственным случаем, когда reinterpret_cast является определенным поведением, является приведение его обратно к типу того, что раньше было reinterpret_cast. Остальные случаи зависят от платформы. - person Öö Tiib; 03.01.2011
comment
@Oo Tiib: это не так. Тот факт, что стандарт конкретно не предписывает поведение, не означает, что поведение не определено четко. Например, там, где я сокращаю: rc<uchar*>(rc<void*>(rc<char*>)) четко определено. - person Yttrill; 03.01.2011
comment
@Brent Arias @Yttrill: Стандарт C++ говорит, что сопоставление, выполняемое reinterpret_cast, определяется реализацией (стандарт ISO C++ 5.2.10/3), поэтому оно в некотором роде зависит от платформы. - person In silico; 03.01.2011
comment
@Yttrill: Да, спасибо, char* из указателей на типы POD — это особый случай, но ОП об этом не просил. - person Öö Tiib; 03.01.2011

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

Из 5.2.10/9 (reinterpret_cast):

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

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

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

person Mark B    schedule 03.01.2011
comment
Если бы я выбрал MyDerived1, мне пришлось бы неправильно преобразовать указатель члена MyDerived2 в MyDerived1. Обратное также применимо. - person nonoitall; 03.01.2011

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

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

Всегда избегайте переинтерпретировать_каст.

ОТРЕДАКТИРОВАНО

Я имею в виду, что с помощью reinterpret_cast вы можете указать неправильный адрес памяти, приведение стиля C обрабатывает все проблемы, связанные с платформой, такие как ABI, выравнивание памяти, размер указателя и т. д.

ОТРЕДАКТИРОВАНО По вдохновению комментаторов я прочитал раздел 5.2.10 "Reinterpret_cast" стандарта ISO/IEC 14882:2003.

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

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

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

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

person 9dan    schedule 03.01.2011
comment
Если это так, то какова цель reinterpret_cast? - person nonoitall; 03.01.2011
comment
Первоначальная идея заключалась в том, чтобы учитывать приведения, чтобы сделать намерение более ясным, поскольку приведения в стиле C - это что-то вроде кухонной раковины / швейцарского армейского ножа: вы даже не можете отличить преобразование от реинтерпретации. К сожалению, приведения в Стандарте не могут правильно разделить пространство и включают как минимум одно абсурдное правило, ограничивающее повторное толкование приведения (когда никакое ограничение не является логически разумным). А жаль, идея была хорошая. - person Yttrill; 03.01.2011
comment
reinterpret_cast это: Я хозяин! у тебя компилятор отваливается! Я знаю, что я делаю!. Действительно очень опасно для обычных людей, таких как мы. - person 9dan; 03.01.2011
comment
К вашему сведению: за исключением случайного dynamic_cast, я обычно использую приведения в стиле C, хотя, по общему признанию, я обычно генерирую C++, а не пишу его вручную. - person Yttrill; 03.01.2011
comment
@9dan: действительно, но тогда C ++ опасен, если вы боитесь приведений, вам следует попробовать разумный язык, такой как Ocaml, который в них не нуждается (15 лет его использования, никогда не писал приведения ... ну, есть' нет, ну как я мог? :) - person Yttrill; 03.01.2011
comment
Так что, если я изменю reinterpret_casts выше на приведения в стиле C, все должно быть совместимым с совместимыми компиляторами? - person nonoitall; 03.01.2011
comment
@nonoitall: это может позволить скомпилировать код, но тогда нет гарантии, что код действительно будет работать. - person In silico; 03.01.2011
comment
ИМХО, если он успешно скомпилирован, нет причин, чтобы что-то пошло не так. - person 9dan; 03.01.2011
comment
-1, совершенно неправильно. Приведения типов C описываются в терминах других приведений, включая reinterpret_cast<>. Очевидно, это опровергает утверждение, что так всегда лучше. Но это еще хуже. В стандарте может быть явно указано, какой тип приведения C++ используется для приведения в стиле C, но программист, пишущий или читающий код, может иметь неверные предположения. Таким образом, именованные приведения C++ (включая reinterpret_cast) строго не хуже (а часто и лучше), чем приведение стилей C. Другая причина работы приведения в стиле -1: C не зависит от платформы, опять же потому, что они могут сопоставляться с reinterpret_cast - person MSalters; 03.01.2011
comment
@MSalters Что ты хочешь сказать? Моя точка зрения заключается в том, чтобы использовать или не использовать reinterpret_cast. Предположим, у вас есть два варианта: C cast и reinterpret_cast. Какой из них вы выберете? - person 9dan; 03.01.2011
comment
@MSalters Я сказал, что если приведение в стиле C работает, это действительная платформа независимо. В этом вопросе это указатель на указатель. Я действительно ошибаюсь? Существуют ли какие-либо компиляторы, которые компилируются без ошибок и неправильно работают с указателем на указатель?? - person 9dan; 03.01.2011
comment
@MSalters ах .. Приведения C описываются с точки зрения других приведения, это ваше недоразумение. Приведение C не может быть описано/реализовано с помощью приведения C++. Более того, детали реализации reinterpret_cast вообще не определены. Карта приведения в стиле C для reinterpret_cast ?? НИ ЗА ЧТО - person 9dan; 03.01.2011
comment
Приведение в стиле C почти всегда делает то же самое, что и одно или два преобразования в C++. Причина, по которой все программисты на C++ должны избегать приведения типов в стиле C, заключается в том, что не очевидно, какие из static_cast, reinterpret_cast и/или const_cast происходят на самом деле. - person aschepler; 04.01.2011
comment
@aschepler Частично верно, частично неверно. Приведение в стиле C выполняет различные комбинации приведения C++ за кулисами. reinterpret_cast — это всего лишь зелье этой операции. В нечетных случаях вам нужно выполнять различные приведения C++ и корректировать адрес, чтобы добиться тех же эффектов, что и приведение в стиле C. - person 9dan; 04.01.2011
comment
@9dan: карта приведения в стиле C для reinterpret_cast ?? НИКАК - лучше перечитайте 5.4.5 в стандарте или проверьте stackoverflow.com/questions/674982/ . Когда мне приходится выбирать между приведением в стиле C и приведением reinterpret_cast, это происходит потому, что у меня есть тип источника S и тип назначения D, которые нельзя преобразовать с помощью static_cast или dynamic_cast. (Снова см. 5.4.5.) Таким образом, правильным приведением является reinterpret_cast<D>(S_expr), что делает связь между S и D очевидной для читателей кода. - person MSalters; 04.01.2011
comment
@MSalters, если static_cast и dynamic_cast неприменимы, вы можете попробовать reinterpret_cast. Но если вы действительно переинтерпретируете_приведение без стандарта, убедитесь, что у вас есть пригодный для использования целевой тип. Не гарантируется, что reinterpret_cast вернет безопасно используемый указатель. Таким образом, вы попадаете в очень опасную область, определенную реализацией. ИМХО, только непрофессиональные программисты на С++ используют reinterpret_cast в своих любимых проектах. - person 9dan; 04.01.2011
comment
@aschepler прочитал готово. Что вы имеете в виду под пунктом 5.4.? Как насчет этого отрывка из раздела 5.4. В дополнение к этим преобразованиям могут выполняться следующие операции static_cast и reinterpret_cast (за которыми может следовать операция const_cast) с использованием нотации приведения явного преобразования типов, даже если тип базового класса недоступен: - person 9dan; 04.01.2011