用c ++

时间:2017-06-29 22:01:47

标签: c++ visitor dynamic-dispatch

我正在用c ++构建一个动画库。该库将包括一个用于建模和渲染场景的系统。系统的要求是

  1. 建模和渲染的分离。有关场景状态的信息应与渲染场景的过程分开存储。
  2. 可扩展建模和渲染。如果库本身定义了一个node类,那么库的用户应该能够定义一个扩展custom_node功能的新类型node(可能通过继承,但可能通过一些其他手段)。然后,用户应该能够指定用于呈现custom_node的自定义过程。在这样做时,用户应该能够以某种方式利用库中已存在的渲染过程。用户还应该能够定义用于渲染库节点的新过程。 添加:用户应该能够定义整个渲染系统并选择用于渲染场景的渲染系统。例如,假设该库包括照片级真实渲染系统,但是用户想要使用准系统渲染系统渲染场景。用户应该能够使用动画库在动画循环期间在引擎盖下使用的公共渲染界面来实现这样的渲染器(渲染帧,更新场景,渲染下一帧等)。
  3. 库的封装。为了将库的功能扩展到自定义node和渲染过程,用户不需要编辑库的底层代码
  4. 失败方法:使用node s树作为场景模型。子类node用于创建新节点类型。由于节点的子节点类型可能在运行时才知道,因此节点的子节点存储在vector<std::shared_ptr<node>>中。 还定义顶级renderer类和子类renderer以提供特定类型的渲染。

    class image;
    
    class node {
        virtual image render(renderer &r) {return r.render(*this);}
        std::vector<std::shared_ptr<node>> children;
        std::weak_ptr<node> parent;
        // ...
    }
    
    class renderer {
        image render(node &n) {/*rendering code */}
    // ...
    }
    

    要渲染场景,请定义渲染器

    renderer r{};
    

    并使用您喜欢的遍历方法遍历节点树。当您遇到每个std::shared_ptr<node> n时,请致电

    n->render(r);
    

    这种方法将建模和渲染分开,并且它允许可扩展性。要创建custom_node,库的用户只需将node

    作为子类
    class custom_node : public node {
        virtual image render(renderer &r) override {return r.render(*this)}
    }
    

    这种方法可以正常工作,直到我们尝试提供自定义方式来呈现custom_node。为此,我们尝试继承renderer并重载render方法:

    class custom_renderer : public renderer {
        image render(custom_node &n) {/*custom rendering code*/}
    }
    

    本身,这不起作用。考虑:

    renderer &r = custom_renderer{};
    std::shared_ptr<node> n = std::make_shared<custom_node>{};
    n->render(r); // calls renderer::render(node &)
    

    为了根据需要调用custom_renderer :: render(custom_node&amp; n),我们需要在原始渲染器类中添加虚拟重载:

    class renderer {
        image render(node &n) {/*rendering code */}
        virtual image render(custom_node &n) = 0;
    }
    

    不幸的是,这会破坏库的封装,因为我们编辑了其中一个库类。

    那么,我们如何设计一个满足所有3个要求的系统呢?

1 个答案:

答案 0 :(得分:0)

键入擦除。该库提供了render(some_data)函数。

我们从几种节点开始。基元是渲染(基元)只绘制某些东西的节点。

列表节点有子节点,render(list_node)绘制其内容。

generic_node存储任何具有渲染(?)重载的内容。它的类型会删除渲染(?)操作。调用render(generic_node)会对包含的数据调用该类型擦除操作。

list_node包含generic_nodes的向量。

为了添加新的渲染类型,您只需定义一个新类型,重载渲染(new_type),然后将其存储在generic_node中。

这是一个基本的实现:

struct render_target {
  // stuff about the thing we are rendering on
};
struct renderable_concept {
  virtual ~renderable_concept() {}
  virtual void render_on( render_target* ) const = 0;
};
template<class T>
void render( render_target*, T const& ) = delete; // by default, nothing renders

struct emplace_tag {};
template<class T>
struct renderable_model : renderable_concept {
  T t;
  template<class...Us>
  renderable_model( emplace_tag, Us&&...us ):
    t{std::forward<Us>(us)...}
  {}
  void render_on( render_target* target ) const final override {
    render( target, t );
  }
};
template<class T>
struct emplace_as {};
struct generic_node {
  friend void render( render_target* target, generic_node const& node ) {
    if (!node.pImpl) return;
    node.pImpl->render_on(target);
  }
  template<class T, class...Us>
  generic_node( emplace_as<T>, Us&&... us):
    pImpl( std::make_shared<renderable_model<T>>(emplace_tag{}, std::forward<Us>(us)...) )
  {}
  generic_node() = default;
  generic_node(generic_node&&)=default;
  generic_node(generic_node const&)=default;
  generic_node& operator=(generic_node&&)=default;
  generic_node& operator=(generic_node const&)=default;
private:
  std::shared_ptr<renderable_concept> pImpl;
};

现在,如何制作列表节点。

struct list_node {
  std::vector<generic_node> nodes;
  friend void render( render_target* target, list_node const& self ) {
    for (auto&& node:self.nodes)
      render(target, node);
  }
  list_node(std::vector<generic_node> ns):nodes(std::move(ns)) {}
  list_node() = default;
  list_node(list_node&&)=default;
  list_node& operator=(list_node&&)=default;
};

template<class T, class...Args>
generic_node make_node( Args&&... args ) {
  return {emplace_as<T>{}, std::forward<Args>(args)...};
}
template<class T>
generic_node make_node( T&& t ) {
  return {emplace_as<std::decay_t<T>>{}, std::forward<T>(t) };
}

渲染时打印hello world的节点怎么样?

struct printing_node {
  std::string message;
  friend void render( render_target* target, printing_node const& self ) {
    std::cout << self.message;
  }
};

测试代码:

auto list = make_node( list_node{{
  make_node( printing_node{{"hello"}} ),
  make_node( printing_node{{"world"}} )
}});
render_target target;
render(&target, list);

Live example

通用节点是基于不可变共享指针的值类型,在复制时几乎不起作用。