Динамическая проверка отливки из пустоты*

Скажем, у меня есть void*, содержащий указатель на неизвестный class. Я хочу использовать dynamic_cast для проверки во время выполнения типа класса, который у меня есть. Например:

class Foo {};

void* bar = new Foo;

Если я попытаюсь сделать dynamic_cast<Foo*>(bar), я получу:

'void *': недопустимый тип выражения для dynamic_cast

Однако мне нужен dynamic_cast, потому что в моей реальной ситуации я не уверен, что bar на самом деле является Foo*.

Я прочитал здесь, что одним из решений этого является создание базового класса для всех объектов, которые bar могут содержать, reinterpret_cast для указатель на этот базовый класс, а затем попытаться dynamic_cast от этого указателя объекта до Foo.

Для меня это сложно, потому что не все объекты, которые могут храниться в bar, находятся под моим контролем. (И потому, что попытка воссоздать Java вызывает у меня изжогу.) Есть ли другой способ сделать это?


person Jonathan Mee    schedule 27.10.2015    source источник
comment
Тогда вам понадобится какое-то дополнительное состояние, чтобы сказать вам, какой это тип.   -  person James Adkison    schedule 27.10.2015
comment
Создайте интерфейс/абстрактный класс и объявите панель этого типа.   -  person Richard Dally    schedule 27.10.2015
comment
@LeFlou Ewww... Мне не нравится идея оберток, но, честно говоря, это может быть лучшим ответом. Если ни у кого нет лучшего предложения, вы можете написать его, потому что это, вероятно, правильный ответ.   -  person Jonathan Mee    schedule 27.10.2015
comment
@JamesAdkison Верное предложение, но в моем случае я считаю, что это окажется сложнее, чем обертки LeFlou   -  person Jonathan Mee    schedule 27.10.2015
comment
связанные: stackoverflow.com/questions/9099384/   -  person NathanOliver    schedule 27.10.2015
comment
@KarolyHorvath Да, java.lang.Object является основой class для всего в Java.   -  person Jonathan Mee    schedule 27.10.2015
comment
Я считаю, что даже с добавлением базового класса решение dynamic_cast работает только в том случае, если указатель действительно указывает на что-то, происходящее от этой базы; если вы хотите настаивать на этом, вы должны заставить свой интерфейс принимать указатели на базу, а не пустые указатели!   -  person    schedule 27.10.2015
comment
@JonathanMee Если вы не контролируете объекты, хранящиеся в bar, как вы можете гарантировать, что они происходят от определенного базового класса?   -  person James Adkison    schedule 27.10.2015
comment
@Hurkyl Верно, конечно.   -  person Jonathan Mee    schedule 27.10.2015
comment
@JamesAdkison Я не контролирую определения объектов, хранящихся в bar. И я не могу ничего гарантировать в отношении них, поэтому мне нужен dynamic_cast для проверки типов во время выполнения.   -  person Jonathan Mee    schedule 27.10.2015
comment
Да, я знаю, что вы их не контролируете, это моя точка зрения о предложении создать интерфейс, предложенный ЛеФлоу (т. Е. Вы не можете гарантировать, что типы получают интерфейс).   -  person James Adkison    schedule 27.10.2015
comment
@JamesAdkison Как упоминалось здесь правильный способ справиться с этим — создать оболочки для объектов, реализации которых я не контролирую, и использовать эти оболочки в своем коде. (Очевидно, что оболочки будут наследоваться от базового класса.)   -  person Jonathan Mee    schedule 27.10.2015
comment
Ладно, это отключение. Я думал, что объекты, которые могут храниться в баре, не все находятся под моим контролем, что означает, что вы даже не контролировали его в момент вызова (т.е. не только класс не находится под вашим контролем, но и точка вызова, которая приводит к void* не находился под вашим контролем). Если вы контролируете точку вызова, то да, вы можете гарантировать, что используете оболочку.   -  person James Adkison    schedule 27.10.2015
comment
@JamesAdkison Да, к сожалению, ответ ЛеФлоу этого не отражает. Я попросил его обновить... посмотрим, что будет...   -  person Jonathan Mee    schedule 27.10.2015


Ответы (3)


dynamic_cast используется для приведения полиморфного объекта к классу, который имеет тип объекта, который вы пытаетесь преобразовать, в качестве его родителя.

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

dynamic_cast знают, что есть базовый класс, и могут выполнять проверку типов через RTTI.

Когда вы сбрасываете пустой указатель, вы говорите компилятору: «Да, вы знаете это место в памяти? Ну, используйте его как этот тип», и если память недействительна, вызывается UB.

у вас есть три варианта здесь.

Вариант 1 Используйте интерфейс. Что ж, полиморфный базовый класс — единственный способ сделать dynamic_cast. Другого пути нет, никаких хаков, это единственный путь. Просто как тот.

struct Base { virtual ~Base() = default; };

struct Derived : Base {};

// ...

void test (Base* base) {
    auto derived = dynamic_cast<Derived*>(base);

    if (derived) {
        // derived is valid here.
    }
}

Вариант 2 Определите тип с помощью указателя. Я использую метод, чтобы иметь уникальный идентификатор для каждого типа и использовать идентификатор для проверки приведения. Сделано без RTTI

using type_id_t = void(*)();
template <typename T> void type_id() {}

// now we can use a map or a vector.
std::vector<std::pair<type_id_t, void*>> vec;

template<typename T>
void insertToMyVector(T* obj) {
    vec.emplace_back(type_id<T>, obj);
}

template<typename T>
T* getObj(int index) {
    auto item = vec[index];

    return static_cast<T*>(item.first == &type_id<T> ? item.second : nullptr);
}

// ...

int main () {
    auto foo = new Foo;

    insertToMyVector(foo);

    auto maybeFoo = getObj<Foo>(0);

    if (maybeFoo) {
        // you have a valid Foo here
    }
}

Вариант 3. Создание производного класса для любого типа. Этот вариант весьма полезен, поскольку он может содержать любой тип, сохраняя при этом безопасность типов. Я похож на решение 1, но предлагаю больше гибкости. Хитрость заключается в создании производного класса для любого типа с использованием шаблонов. Преимущество в том, что вы можете держать любой тип, но это может немного усложнить вам обучение.

struct Base { virtual ~Base() = default; };

template<typename T>
struct Derived : Base {
    Derived(T&& obj) : _obj{std::move(obj)} {}
    Derived(const T& obj) : _obj{obj} {}

    T& get() {
        return _obj;
    }

    const T& get() const {
        return _obj;
    }

private:
    T _obj;
};

// ...

void test (Base* base) {
    auto derived = dynamic_cast<Derived<int>*>(base);

    if (derived) {
        int i = derived->get();
        // derived is valid here, and we can safely access the int
    }
}
person Guillaume Racicot    schedule 27.10.2015
comment
1 Было предложено по ссылке в вопросе и не является желательным решением. 2 Сначала было предложено здесь, но это не очень хорошее решение для моей проблемы. 3 Описано ли решение здесь и на данный момент является наиболее многообещающим. - person Jonathan Mee; 27.10.2015
comment
2 Можно улучшить, используя type_index и < b>3 Можно улучшить, добавив оператор приведения вместо get: operator T() { return _obj; } - person Jonathan Mee; 27.10.2015
comment
Я бы предпочел добавить operator* и operator->, чем оператор преобразования. Или сделать оператор преобразования явным - person Guillaume Racicot; 27.10.2015
comment
Можете ли вы помочь мне понять, почему сделать обертку невидимой с помощью оператора приведения было бы плохой идеей? - person Jonathan Mee; 27.10.2015
comment
Что ж, оператор преобразования будет копировать базовый объект, а иногда это нежелательно, особенно при работе с тяжелыми объектами. Если бы это было неявно, мне было бы очень легко создать проблемы с производительностью/памятью или нежелательные копии. Что касается operator* и operator->, то нет. Если вам действительно нужен оператор преобразования, его явное указание уменьшит количество ошибок и сделает их легко отслеживаемыми. - person Guillaume Racicot; 27.10.2015

Чтобы обеспечить компиляцию и работу dynamic_cast, вы должны создать абстрактный или интерфейсный класс с виртуальным методом.

#include <cassert>

class Bar
{
public:
    Bar() = default;
     virtual ~Bar() = default;
};

class Foo : public Bar
{
public:
    Foo() = default;
    virtual ~Foo() = default;
};

int main()
{
    Bar* bar = new Foo;
    Foo* foo = dynamic_cast<Foo*>(bar);
    assert(foo != nullptr);
}
person Richard Dally    schedule 27.10.2015
comment
Но я думаю, что ваш комментарий больше, чем это правильно? В частности, дочерний элемент Bar должен быть оболочкой для другого объекта. - person Jonathan Mee; 27.10.2015
comment
Это работает здесь, но ничего не делает для решения проблемы, с которой сталкивается OP, поскольку OP не имеет контроля над некоторыми объектами, переданными его функции. Поэтому он не может сделать их производными от общего объекта. - person NathanOliver; 27.10.2015

Насколько я понимаю, вам нужен полиморфный объект, но не общий базовый класс.

Для этого уже есть довольно стандартная идиома — она называется boost::any.

boost::any содержит ваш объект плюс некоторую информацию о типе. Интерфейс позволяет вам запрашивать тип и пытаться привести любой к типу, который вы ищете.

http://www.boost.org/doc/libs/1_59_0/doc/html/any.html

person Richard Hodges    schedule 27.10.2015
comment
Я ненавижу давать +1 для повышения ответов, но на данный момент это лучшая версия: stackoverflow.com/questions/33370296/ - person Jonathan Mee; 27.10.2015
comment
Я ненавижу давать форсированные ответы, но зачем заново изобретать идеально круглое колесо? - person Richard Hodges; 27.10.2015
comment
:) Поэтому я не могу найти для этого никакой реализации, но мне очень любопытно, как это было сделано. Где-нибудь я могу просмотреть источник для этого? - person Jonathan Mee; 27.10.2015
comment
конечно. загрузите источник повышения и посмотрите! По сути, это достигается с помощью внутренней полиморфной концепции, реализации которой основаны на типе объекта, переданном в конструктор (или оператор присваивания). Концепция поддерживает извлечение идентификатора типа и указателя на адрес сохраненного объекта. - person Richard Hodges; 27.10.2015
comment
Я считаю, что any теперь актуальна. Я считаю, что это выполняет часть boost::any, на которую вы ссылались. Если бы вы могли заменить это стандартным any и привести пример, я бы согласился. - person Jonathan Mee; 07.01.2019