2010-03-04 1 views
7

Моя программа должна использовать void * для переноса данных или объектов в динамическую ситуацию вызова, чтобы она могла ссылаться на данные произвольных типов, даже примитивных типы. Тем не менее, я недавно обнаружил, что процесс сбрасывания этих void * в случае классов с несколькими базовыми классами терпит неудачу и даже сбой моей программы после вызова методов на этих скрытых указателях, даже если адреса памяти кажутся правильными. Авария происходит во время доступа к «vtable».множественное наследование: неожиданный результат после отливки от void * до второго базового класса

Таким образом, я создал небольшой тестовый случай, среда 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 

Как вы можете видеть, «декорирован (per void *) "результат совершенно неправильный. Он также должен быть 30,30, как в первой строке.

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

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

Благодаря

+1

Вы можете использовать reinterpret_cast для приведения в действие void *, а затем вернуться к исходному типу. Вы можете __NOT__ отбрасывать на void *, а затем на что-нибудь еще. –

+0

Вам будет намного лучше использовать шаблон декоратора, чем MI, а не тот, который разрешил бы литье из проблемы void *. – quamrana

ответ

7

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

Square 
Decorated 
DecoratedSquare specific stuff 

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

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

+0

Это объясняет это, но это будет означать, что без знания конкретного подкласса я не могу создать код, который работает с указателями на один из базовых классов полиморфным способом, как это было бы возможно в ситуациях одиночного наследования, когда вызывающий необходимо знать конкретный подкласс. Другими словами, было бы невозможно собрать структуру и использовать ее позже, когда известны конкретные подклассы? Это означает, что множественное наследование в C++ не является полным. –

+2

MI in C++ завершен, если вы не выбросите информацию о типе. Вы можете использовать «виртуальные» функции для получения полиморфного поведения, не зная конкретных подклассов, если вы не бросаете дико на неправильные типы. –

+0

Мне, поскольку я просто не знаю конкретного подтипа в той части рамки, это просто означает, что я не могу использовать void * там. Я должен нести информацию о типе как-то с помощью «любого» или тому подобного. Нехорошо и не то, что я ожидал :(но, по крайней мере, вы дали мне хорошее объяснение. Спасибо всем –

2

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

+0

, так это значит, что void * невозможно в нескольких ситуациях наследования? –

+1

Это возможно, если вы всегда бросаете фактический тип объекта, а не базовый класс. –

+0

см. Мой комментарий к отвечу Майка –

9

Повторите десять раз - единственное, что вы можете смело сделать с указателем 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), который вам нужен.

+0

Стив благодарен за это. Я очень его суммирую. В самом деле, всегда обеспечивается, что в вызывающей части структуры существует известный тип базового указателя, который передается в void * и затем переносится на принимающий конец, который знает 2 вещи: самый верхний базовый класс и некоторый подкласс, где метод определено. Однако он не знает конкретного подкласса. Но теперь это выглядит нормально, так как принимающая сторона всегда возвращает reinterpret_casts к тому же самому верхнему базовому классу, что и конец отправки, а затем делает dynamic_cast или static_cast промежуточному подклассу, где он вызывает желаемый метод. –

+0

+1 для «единственное, что вы можете смело сделать с помощью указателя reinterpret_cast, - это reinterpret_cast его обратно к тому же типу указателя, из которого он пришел». На самом деле это очень хорошо подходит. –

 Смежные вопросы

  • Нет связанных вопросов^_^