множественное наследование: неожиданный результат после приведения из void * во 2-й базовый класс - PullRequest
8 голосов
/ 04 марта 2010

В моей программе необходимо использовать void * для переноса данных или объектов в ситуации динамического вызова, чтобы она могла ссылаться на данные произвольных типов, даже примитивных типов. Однако недавно я обнаружил, что процесс преобразования этих void * в случае классов с несколькими базовыми классами завершается неудачно и даже вызывает сбой моей программы после вызова методов для этих указателей, даже если адреса памяти кажутся правильными. Сбой происходит при доступе к "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 (), я всегда получу одинаковые неожиданные результаты для детали Decorated. Что-то совершенно не так с этими пустотами *.

Из моего понимания C ++ это должно сработать. Есть ли шанс заставить это работать с пустотой *? Может ли это быть ошибка в gcc?

Спасибо

Ответы [ 3 ]

12 голосов
/ 04 марта 2010

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

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

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

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

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

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

9 голосов
/ 04 марта 2010

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

Square
Decorated
DecoratedSquare specific stuff

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

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

2 голосов
/ 04 марта 2010

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

...