Безопасный способ динамического приведения типа к типу? - PullRequest
1 голос
/ 05 марта 2019

Мой C ++ немного ржавый, и я не помню все в стандарте.

У меня есть void*.В одной конкретной функции это либо класс, который наследует альфа, либо класс, который наследует бета.Оба базовых класса имеют виртуальные функции.Однако я не могу сказать, что есть какой

class Alpha {
public:
    virtual void Speak() { printf("A"); }
};
class Beta {
public:
    virtual void Speak() { printf("B"); }
};
int main(){
    //BAD CODE WILL PRINT TWICE
    void *p = new Alpha;
    Alpha*a = dynamic_cast<Alpha*>((Alpha*)p);
    Beta*b = dynamic_cast<Beta*>((Beta*)p);
    if(a)
        a->Speak();
    if(b)
        b->Speak();
    return 0;
}

Как мне определить, какой класс какой?В этой кодовой базе есть сотни классов, которые преобразуются в void.Большинство из них наследуют 5 базовых классов, но я не хочу это выяснять.Является ли единственное решение, унаследованное от чего-то вроде class Dummy {public: virtual void NoOp(){}}; и приведенное к Dummy перед использованием динамического приведения?Это безопасно?Я надеюсь, что есть лучшее решение, но я не могу думать ни о чем другом.

Ответы [ 4 ]

5 голосов
/ 05 марта 2019

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

Что вы могли бы сделать в вашем случае, это определить

class Base
{
    public:
    virtual ~Base() = default; // make me a polymorphic type and make 
                               // polymorphic delete safe at the same time.
};

и сделайте это базовым классом для Alpha и Beta. Затем обведите указатель Base* вместо void* и поместите dynamic_cast прямо на p.

Обратите внимание, что если вы объявите virtual void Speak() = 0; в Base, то ваш код в main станет просто

int main(){ 
    Base* p = new Alpha;
    p->Speak();
    delete p; // ToDo - have a look at std::unique_ptr
}

Как правило, броски любого рода нежелательны.

1 голос
/ 05 марта 2019

Если вы используете приведение в стиле C для преобразования в Alpha*, аналогично static_cast перед использованием динамического приведения, то динамическое приведение не дает никакого эффекта.здесь ваш код выполняется, потому что оба класса имеют одинаковый интерфейс, но на самом деле это неопределенное поведение.

Обычно вы хотите использовать динамическое приведение к восходящему / нисходящему из / в базовый класс в / из его производного класса,

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

#include <stdio.h>

class Speaker {
public:
  virtual void Speak() = 0;
};

class Alpha: public Speaker {
public:
  virtual void Speak() { printf("A"); }
};

class Beta: public Speaker {
public:
  virtual void Speak() { printf("B"); }
};

int main(){
  void *p = new Alpha;
  // Convert to base type, using static_cast                                    
  Speaker *s = static_cast<Speaker *>(p);
  // Attempt Upcasts                                                            
  Alpha*a = dynamic_cast<Alpha*>(s);
  Beta*b = dynamic_cast<Beta*>(s);

  // See if it worked                                                           
  if (a)
    a->Speak();
  if (b)
    b->Speak();
  return 0;
}

Выходы: A

1 голос
/ 05 марта 2019

Выражение Alpha*a = dynamic_cast<Alpha*>((Alpha*)p); сначала приводит p к Alpha* с явным приведением в стиле c . Затем полученный результат Alpha* пропускается через dynamic_cast<Alpha*>. Использование dynamic_cast<T*> для указателя T* (указатель того же типа, который вы пытаетесь привести) всегда будет обеспечивать входной указатель. Его нельзя использовать для подтверждения правильности указателя. От cppreference для dynamic_cast<new_type>(expression):

Если тип expression точно равен new_type или версия new_type с меньшей квалификацией cv, результатом будет значение expression с типом new_type.

В результате код всегда будет компилироваться и выполняться, а система типов не будет вас защищать. Но итоговое поведение не определено. В случае Beta*b = dynamic_cast<Beta*>((Beta*)p); вы говорите компилятору доверять, что p - это Beta*, но это не так. Разыменование результирующего указателя является неопределенным поведением, и dynamic_cast не может защитить вас от этой ошибки.

Если вы попытаетесь удалить явное преобразование типа, вы получите ошибку компилятора. dynamic_cast требует указателя или ссылки на полный тип, а void не является полным типом. Вам нужно будет найти способ отследить фактический тип, указанный вам, и явно преобразовать p в этот тип указателя перед использованием dynamic_cast. Хотя в этот момент, если вы уже знаете тип, на который вы хотите выполнить приведение, он может больше не понадобиться.

Попробуйте вместо этого использовать общий базовый тип или, если необходимо, используйте std::variant или std::any.

0 голосов
/ 05 марта 2019

Вы должны знать тип, из которого был преобразован указатель void. Если вы не знаете динамический тип, то вы должны создать указатель void из указателя на базу. Если вы не знаете тип указателя, из которого был создан пустой указатель, вы не можете использовать пустой указатель.

Учитывая, что указатель void был преобразован из Alpha*, вы можете преобразовать его обратно, используя статическое приведение:

auto a_ptr = static_cast<Alpha*>(p);

Затем вы можете использовать dynamic_cast для преобразования в производный тип.

if(auto d_ptr = dynamic_cast<DerivedAlpha*>(a_ptr)) {
    // do stuff with DerivedAlpha

В случае, если динамический тип не DerivedAlpha, динамическое приведение безопасно вернет ноль. Вы не можете динамически отбрасывать от иерархии типов. Поскольку Alpha и Beta не связаны какой-либо структурой наследования, они не могут быть динамически преобразованы взаимно.

...