You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免vector<unique_ptr<Element>>元素的dynamic_cast,实现关注点分离的处理器调用?

问题描述

我正在改进自己之前设计的一个C++方案,目前的设计存在缺陷。核心需求如下:

  • 存在一个Element基类,被多个子类继承;
  • 存在一个DrawHandler基类,被多个子类继承;
  • 存在一个Surface类,维护Element列表,并通过调用对应DrawHandler渲染这些元素。

这个设计需要支持通过新增Element子类和DrawHandler子类扩展项目,无需修改现有代码。此外,后续还可能加入不同类型的处理器,比如CommandHandler等。

当前临时方案

Surface类维护一个std::vector<std::unique_ptr<Element>> elements成员,其renderFrame()方法实现如下:

for(const auto& element: elements){
    auto handler =  Tester::getHandler(*element);
    handler->draw(*this, *element);
}

我对Tester::getHandler的实现非常不满意,它的逻辑大致如下:

static std::unique_ptr<DrawHandler> getHandler(const Element& element){
    if (auto* c = dynamic_cast<const Subclass1*>(&element)) {
      return std::make_unique<SubClass1DrawHandler>();
    }
    if (auto* c = dynamic_cast<const Subclass2*>(&element)) {
      return std::make_unique<Subclass2DrawHandler>();
    }
    // ... 更多类型判断
}

尝试过的失败方案

我试过几种方法,但因为对C++不够精通,始终没找到比上述方案更好的实现:

  • 强制Element子类实现getHandler会污染设计,违反SOLID原则。而且如果处理器类型增多(比如DrawHandler、ControlHandler等),每个Element子类都要新增对应的getHandler方法,扩展性极差。
  • 第一次尝试模板失败:std::vector<std::unique_ptr<Element>>会擦除类型信息,导致模板无法获取具体的子类类型:
template <typename ElementType>
void drawElement(const ElementType& element){
  // 这里ElementType == Element,子类特化信息被擦除
}
void someFunc(){
  // elements是vector<unique_ptr<Element>>,已存入Subclass1等子类实例
  for(const auto& element: elements){
    drawElement(element);
  }
}
  • 第二次尝试模板时,我把elements改成vector<pair<unique_ptr<Element>, unique_ptr<DrawHandler>>>,并修改Surface::addElement方法:
template <typename T>
void addElement(T&& element) {
  using DecayedType = std::decay_t<T>;
  auto elPtr= std::make_unique<DecayedType>(std::forward<T>(element));
  auto handlerPtr = std::make_unique<typename DrawHandlerTraits<DecayedType>::HandlerType>();
  elements.emplace_back(std::move(elPtr), std::move(handlerPtr));
}

对应的DrawHandlerTraits实现大致如下:

template <typename ElementType>
struct DrawHandlerTraits;

template <>
struct DrawHandlerTraits<Subtype1>{
  using HandlerType = Subtype1DrawHandler;
};

template <>
struct DrawHandlerTraits<Subtype2>{
  using HandlerType = Subtype2DrawHandler;
};

// ... 更多特化

但这种方案只是把dynamic_cast的条件判断换成了模板特化,本质上还是要针对每个新增类型修改代码。而且如果新增处理器类型,还得修改elements的结构,比如有四种处理器时,elements会变成:

vector<tuple<unique_ptr<Element>, unique_ptr<DrawHandler>, unique_ptr<CommandHandler>, unique_ptr<AudioHandler>, unique_ptr<TextDescriptionHandler>>> elements;
需求

我希望找到一种设计模式,能支持如下用法:

Subtype1 x;
Handler[Draw[Subtype1]] handler1 ; // 实际类型为Subtype1DrawHandler

Subtype2 y;
Handler[Command[Subtype2]] handler2 ; // 实际类型为Subtype2CommandHandler
Handler[Draw[Subtype2]] handler3; // 实际类型为Subtype2DrawHandler

这种用法可以用模板实现,但存在一个核心问题:

// ...
surface.addElement(x);
surface.addElement(y);
surface.render();
// ...

Surface::render(){
  // elements是vector<unique_ptr<Element>>,此时Subtype1和Subtype2的类型信息已被隐藏
  // 但模板需要这些具体类型信息才能工作
}

内容的提问来源于stack exchange,提问作者AVALFINN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 13:53:13