множественное наследование: неожиданный результат, после того, как брошено от пустоты * к 2-му базовому классу

Моя программа должна использовать пусто* для переноса данных или объектов в динамической ситуации с вызовом, так, чтобы это могло сослаться на данные произвольных типов, даже типов примитивов. Однако я недавно обнаружил, что процесс вниз бросающих, которые они освобождают* в случае классов с несколькими сбоями базовых классов и даже разрушают мою программу после вызова методов на них вниз литые указатели, даже если адреса памяти, кажется, корректны. Катастрофический отказ происходит во время доступа к "vtable".

Таким образом, я создал небольшой тестовый сценарий, среда является gcc 4.2 на Mac OS X:

class Shape {
public:
    virtual int w() = 0;
    virtual int h() = 0;
};

class Square : public Shape {
public:
    int l;
    int w() {return l;}
    int h() {return l;}
};

class Decorated {
public:
    int padding;
    int w() {return 2*padding;}
    int h() {return 2*padding;}
};

class DecoratedSquare : public Square, public Decorated {
public:
    int w() {return Square::w() + Decorated::w();}
    int h() {return Square::h() + Decorated::h();}
};


#include <iostream>

template <class T> T shape_cast(void *vp) {
//    return dynamic_cast<T>(vp);   // not possible, no pointer to class type
//    return static_cast<T>(vp);
//    return T(vp);
//    return (T)vp;
    return reinterpret_cast<T>(vp);
}

int main(int argc, char *argv[]) {
    DecoratedSquare *ds = new DecoratedSquare;
    ds->l = 20;
    ds->padding = 5;
    void *dsvp = ds;

    std::cout << "Decorated (direct)" << ds->w() << "," << ds->h() << std::endl;

    std::cout << "Shape " << shape_cast<Shape*>(dsvp)->w() << "," << shape_cast<Shape*>(dsvp)->h() << std::endl;
    std::cout << "Square " << shape_cast<Square*>(dsvp)->w() << "," << shape_cast<Square*>(dsvp)->h() << std::endl;
    std::cout << "Decorated (per void*) " << shape_cast<Decorated*>(dsvp)->w() << "," << shape_cast<Decorated*>(dsvp)->h() << std::endl;
    std::cout << "DecoratedSquare " << shape_cast<DecoratedSquare*>(dsvp)->w() << "," << shape_cast<DecoratedSquare*>(dsvp)->h() << std::endl;
}

производит следующий вывод:

Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30

Как Вы видите, "Украшенный (на пустоту*)" результат является абсолютно неправильным. Это должно также быть 30,30 как в первой строке.

Безотносительно метода броска я использую в shape_cast (), я буду всегда получать те же неожиданные результаты для Украшенной части. Что-то абсолютно неправильно с ними, освобождают *.

От моего понимания C++ это должно на самом деле работать. Там шанс состоит в том, чтобы заставить это работать с пустотой*? Это может быть ошибкой в gcc?

Спасибо

8
задан André Pareis 4 March 2010 в 13:17
поделиться

3 ответа

Это не ошибка компилятора - это то, что делает reinterpret_cast . Объект DecoratedSquare будет размещен в памяти примерно так:

Square
Decorated
DecoratedSquare specific stuff

Преобразование указателя на него в void * даст адрес начала этих данных без знания какого типа есть. reinterpret_cast примет этот адрес и интерпретирует все, что там есть, как Decorated , но фактическое содержимое памяти - это Square . Это неправильно, поэтому вы получаете неопределенное поведение.

Вы должны получить правильные результаты, если вы reinterpret_cast к правильному динамическому типу (то есть DecoratedSquare ), а затем конвертируете в базовый класс.

9
ответ дан 5 December 2019 в 07:11
поделиться

Повторите десять раз - единственное, что вы можете безопасно сделать с указателем reinterpret_cast , - это reinterpret_cast , чтобы он вернулся к тот же тип указателя, от которого он произошел. То же самое относится к преобразованию в void * : вы должны преобразовать обратно в исходный тип.

Итак, если вы преобразовали DecoratedSquare * в void * , вы должны преобразовать его обратно в DecoratedSquare * . Не Украшенный * , не Квадрат * , а не Форма * . Некоторые из них могут работать на вашем компьютере, но это сочетание удачи и поведения, зависящего от реализации. Обычно он работает с одиночным наследованием, потому что нет очевидных причин для реализации указателей объектов таким образом, чтобы они перестали работать, но это не гарантируется, и в целом он не может работать для множественного наследования.

Вы говорите, что ваш код обращается к «произвольным типам, включая примитивные типы» через void *. В этом нет ничего плохого - по-видимому, тот, кто получает данные, знает, что следует рассматривать их как DecoratedSquare * , а не как, скажем, int * .

Если тот, кто его получает, знает только, что следует рассматривать его как базовый класс, например Decorated * , то тот, кто преобразует его в void * , должен static_cast это сначала в базовый класс, затем в void * :

void *decorated_vp = static_cast<Decorated*>(ds);

Теперь, когда вы приведете Decorated_vp обратно в Decorated * , вы получите результат static_cast (ds) , что вам и нужно.

12
ответ дан 5 December 2019 в 07:11
поделиться

Статический_каст или динамический_каст при наличии множественного наследования может изменить представление указателя, сместив его так, чтобы он обозначал правильный адрес. Статический_каст определяет правильное смещение, учитывая информацию о статической типизации, а динамический_каст делает это, проверяя динамический тип. Если вы бросаете void*, вы теряете всю информацию о статическом типе и возможность получить информацию о динамическом типе, поэтому используемый вами reinterpret_cast предполагает, что смещение равно null, что иногда приводит к ошибке.

2
ответ дан 5 December 2019 в 07:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: