Добре ли е да прехвърляте променлива указател към член в този случай?

Напоследък опреснявам/актуализирам знанията си за 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::* е общо не-не, но в този случай използвам само променливата pointers-to-member, за да получа указател към обект. (Не се опитвам да копирам производен обект, сякаш е основен обект, нито да поставям основен обект в паметта на производен обект.) Това работи по предназначение на 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 е ​​специален случай, но OP не го поиска. - person Öö Tiib; 03.01.2011

РЕДАКТИРАНЕ: Справка от стандарта. Ако го чета правилно, тъй като не отговаряте на нито едно от изключенията, това, което сте направили, е неуточнено и така може или не може да работи на всеки конкретен компилатор. Няма изключения за вида на свързания член.

От 5.2.10/9 (reinterpret_cast):

Rvalue от тип „указател към член на X от тип T1“ може да бъде изрично преобразуван в rvalue от тип „указател към член на Y от тип T2“, ако T1 и T2 са и двата типа функции или и двата типа обекти.66) Нулевата стойност стойността на указател на член (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.

РЕДАКТИРАНО

Искам да кажа, че с reinterpret_cast можете да посочите грешен адрес на паметта, C стилът cast се справя с всички проблеми, свързани с платформата, като ABI, подравняване на паметта, размер на показалеца и т.н.

РЕДАКТИРАН По вдъхновение на коментаторите, прочетох ISO/IEC 14882:2003 раздел 5.2.10 „Reinterpret_cast“.

Разбира се, моето разбиране е ограничено, но ми е приятно да си спомня защо мразех reinterpret_cast на първо място.

Мисля, че reinterpret_cast липсва или има много ограничено съзнание за йерархията на наследяване.

Ако 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
FYI: освен от време на време dynamic_cast, обикновено използвам отливки в стил C, въпреки че, разбира се, обикновено генерирам C++, вместо да го пиша на ръка. - person Yttrill; 03.01.2011
comment
@9dan: наистина, но тогава C++ е опасен, ако се страхувате от кастинги, трябва да опитате разумен език като Ocaml, който не се нуждае от тях (15 години го използвам, никога не съм писал кастинг .. е, има" никаква, как бих могла? :) - person Yttrill; 03.01.2011
comment
Така че, ако променя reinterpret_casts по-горе на C-style casts, нещата трябва да са съвместими със съвместимите компилатори? - person nonoitall; 03.01.2011
comment
@nonoitall: Може да позволи компилирането на кода, но тогава няма гаранция, че кодът действително ще работи. - person In silico; 03.01.2011
comment
IMHO, ако се компилира успешно, няма причина нещата да се объркат. - person 9dan; 03.01.2011
comment
-1, напълно погрешно. C отливките са описани от гледна точка на други отливки, включително reinterpret_cast<>. Очевидно това опровергава твърдението, че винаги е по-добре. Но е по-лошо от това. Стандартът може да посочи изрично кой тип преобразуване на C++ се използва за преобразуване в стил C, но програмистът, който пише или чете кода, може да има неправилни предположения. Следователно, посочените C++ кастинги (включително reinterpret_cast са строго не по-лоши (и често по-добри) от C stule кастинга. Друга причина за -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 cast не може да бъде описан/имплементиран с C++ cast. Освен това подробностите за изпълнението на 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 не е гарантирано да върне безопасно използваем указател. Така че ефективно попадате в много опасна област, определена за изпълнение. IMHO, само непрофесионални C++ програмисти използват reinterpret_cast в домашни проекти. - person 9dan; 04.01.2011
comment
@aschepler прочетете готово. Какво имате предвид с раздел 5.4.? Какво ще кажете за този откъс от раздел 5.4. В допълнение към тези преобразувания, следните операции static_cast и reinterpret_cast (по желание последвани от операция const_cast) могат да бъдат изпълнени, като се използва нотацията за преобразуване на изрично преобразуване на тип, дори ако типът на базовия клас не е достъпен: ? - person 9dan; 04.01.2011