Динамическое приведение c ++ с вложенным полиморфным шаблоном - PullRequest
0 голосов
/ 28 ноября 2018

Я использую Очередь сообщений PolyM , которая предлагает сообщения

class Msg

и сообщения с полезной нагрузкой шаблона

template<typename PayloadType> class DataMsg: public Msg

Это работает до тех пор, пока я не вложу шаблон DataMsgвнутри другого DataMsg, например так ...

DataMsg<DataMsg<int>>

и попробуйте извлечь вложенный DataMsg, чтобы передать его для дальнейшей обработки.Для этого я применяю базовый тип Msg следующим образом:

function(Msg &base) {
    auto &msg = dynamic_cast<DataMsg<Msg>&>(base).getPayload();
}

Это приведение не выполняется с исключением из-за неправильного приведения.Использование static_cast вместо этого, похоже, не имеет побочных эффектов.

С точки зрения полиморфизма я не вижу ничего плохого в своем подходе.Как динамическое приведение работает для не вложенного типа, оно также должно работать и для вложенного типа?

Я задавал этот вопрос на странице проблем PolyM GitHub , однако не получилправильное объяснение причин сбоя приведения.

Это минималистичный пример, показывающий проблему:

#include <memory>
#include <iostream>

using namespace std;

class Msg {
  public:
     virtual ~Msg() {}
};

template<typename PayloadType>
class DataMsg: public Msg {
  public:
     virtual ~DataMsg() {}

     PayloadType& getPayload() const
     {
       return *pl_;
     }

  private:
    PayloadType* pl_;
};

static void getInnerMsg(Msg &msgMsg) { 
    try { 
        auto &msg = dynamic_cast<DataMsg<Msg>&>(msgMsg).getPayload();
        std::cout << "cast OK" << endl;
    } catch ( std::bad_cast& bc ) {      
        std::cerr << "bad_cast caught: " << bc.what() << endl;
    }
}

и мои тестовые примеры:

int main(int argc, char *argv[])
{    
     Msg                     msg1;
     DataMsg<int>            msg2;
     DataMsg<Msg>            msg3;
     DataMsg<DataMsg<int>>   msg4;

    cout << "expect bad cast (no nested message)" << endl;
    getInnerMsg(msg1);
    cout << "-------------" << endl;
    cout << "expect bad cast (no nested message)" << endl;
    getInnerMsg(msg2);
    cout << "-------------" << endl;
    cout << "expect successful cast (nested message base type)" << endl;
    getInnerMsg(msg3);
    cout << "-------------" << endl;
    cout << "expect successful cast (nested message child type)" << endl;
    getInnerMsg(msg4);

    return 0;
}

Запуск с "g ++test.cpp -o test.x && ./test.x ".Проблема GitHub содержит более полный пример использования.

Ответы [ 3 ]

0 голосов
/ 28 ноября 2018

С точки зрения полиморфизма я не вижу в этом ничего плохого.Как динамическое приведение работает для не вложенного типа, оно также должно работать и для вложенного типа?

Когда дело касается шаблонов, A<B> и A<C> - это разные несвязанные типы, даже если B и C связаны между собой.Использование ссылки, полученной с помощью static_cast, приводит к неопределенному поведению.Чтобы проиллюстрировать, что если бы мы вручную написали вашу результирующую иерархию классов, это будет сродни этому

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

struct foo : base {
    base *p;
};

struct bar : base {
    foo *p;
};

В приведенном выше примере, если динамический тип объекта равен bar, он не может бытьприведенный к foo&, эти типы не находятся в цепочке наследования друг с другом, и динамическое приведение завершится неудачно, как и должно быть.Но static_cast от base& (имеется в виду bar) до foo& будет успешным.Нет проверки во время выполнения, и компилятор поверит вам на слово о типах, но вы не сказали правду.Неопределенное поведение следует, если вы используете эту ссылку.К сожалению, появление «работающего» является действительным проявлением неопределенного поведения.

0 голосов
/ 28 ноября 2018

Это работает, пока я не вложу шаблон DataMsg в другой DataMsg ... попробую извлечь вложенный DataMsg

Пока ваша логика имеет интуитивный смысл для распаковки вложенных сообщений.

У вас есть общее сообщение с общим заголовком, содержащим конкретное динамическое сообщение определенного типа, извлекает определенную полезную нагрузку сообщения, которое содержит другое сообщение другого определенного типа и т. Д.

Все это прекрасно.

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

template<typename PayloadType> class DataMsg: public Msg
    // ...
  private:
    PayloadType* pl_;
};

означает каждое создание экземпляра DataMsg template is-a Msg и он имеет -a указатель на содержащуюся полезную нагрузку.

Теперь каждый экземпляр DataMsg является новым типом DataMsg<X>, который не связан ни с каким другим экземпляром DataMsg<Y>, даже если X и Y связаны (за исключением того, что они оба происходят от Msg).Итак:

DataMsg<DataMsg<int>>

is-a Msg и has-a указатель на DataMsg<int> (который сам is-a Msg), в то время как

DataMsg<Msg>

также является Msg, а имеет указатель на Msg, но все ещеновый тип, не связанный с DataMsg<DataMsg<int>> (за исключением наличия общей базы), даже если типы указателей полезной нагрузки являются конвертируемыми.

Итак, ваши представления о разметке сообщений хороши, но вы не моделируете их правильно всистема типов.Если вы хотите сделать это, вы можете явно специализироваться на вложенных сообщениях:

using NestedMsg = DataMsg<Msg>;

template<typename NestedPayloadType>
class DataMsg<DataMsg<NestedPayloadType>>: public NestedMsg {
  public:
     NestedPayloadType const & getDerivedPayload() const
     {
       // convert nested Msg payload to derived DataMsg
       return dynamic_cast<DataMsg<NestedPayloadType> const &>(this->getPayload());
     }
};

Сейчас DataMsg<DataMsg<int>> действительно is-a DataMsg<Msg>.Как таковой он наследует Msg const& DataMsg<Msg>::getPayload() const, и вы все равно можете получить производный тип полезной нагрузки (DataMsg<int>), вызвав `getDerivedPayload ().

Ваш другой метод getPayload также должен вернуть const ref,КСТАТИ.

0 голосов
/ 28 ноября 2018

Как (кратко) объяснено в ответе на вопрос о github:

function(Msg &base) {
    auto &msg = dynamic_cast<DataMsg<Msg>&>(base).getPayload();
}

Здесь вы пытаетесь привести от Msg к DataMsg<Msg>.Однако вы указываете, что динамический тип base равен DataMsg<DataMsg<int>>.DataMsg<Msg> наследуется от Msg, а DataMsg<DataMsg<int>> наследуется от Msg, но в остальном они не связаны (независимо от отношения между аргументами шаблона).

В более общем смысле, MyClass<Derived> не наследуетfrom MyClass<Base> - (независимо от того, являются ли оба производными одного и того же базового класса), поэтому динамическое приведение одного из / к другому недопустимо (через / из общей базы или нет).

...