【问题标题】:Runtime element substition in a tree structure树结构中的运行时元素替换
【发布时间】:2020-02-16 16:13:30
【问题描述】:

我正在用 C++ 实现一个 REST API 客户端,它需要能够在运行时发现 API 的结构。 REST API 的结构也可能在程序执行期间发生变化。 API 模型基于 json 数组和对象。如果返回一个数组,则表示检索到的元素是一个节点,如果返回一个对象,则表示检索到的元素是一个叶子。

为了表示我正在建模的数据,我决定使用复合模式为用户提供树结构(节点或叶)的通用接口。

为了给我的模型提供一些 GUI 或 CLI 的钩子,我决定使用访问者模式来访问节点和叶子。

我也尝试使用访问者模式来实现模型的发现部分,但它显然不适合该任务,因为我需要将以前分配的派生类型的对象替换为另一种类型(例如:叶子变成节点)。在我看来,使用访问者模式是不可能的,因为对象不能替换自己。

我尝试添加一些容器类来包装我的对象,但我最终遇到了类似的情况,我需要一个对象来替换自己。

我在网上阅读了有关设计模式的信息,试图找到一种可以帮助我的方法,但我没有找到任何合适的。

这是我到目前为止的代码 - 最相关的部分在最后(发现类):

#include <string>
#include <deque>
#include <stack>
#include <iostream>

class Visitor;

class Element{
public:
  Element(std::string name, Element* parent) : name{ name }, parent{ parent }{}
  std::string get_name(){
    return name;
  }
  std::string get_url(){
    std::stack<std::string> url_stack;
    Element* ancestor = parent;
    url_stack.push(name);
    while(ancestor != NULL){
      url_stack.push(ancestor->name);
      ancestor = ancestor->parent;
    }
    std::string url;
    while(!url_stack.empty()){
      url.append(url_stack.top());
      url_stack.pop();
    }
    return url;
  }
  Element* get_parent(void){
    return parent;
  }
  virtual ~Element(void){};
  virtual void accept(Visitor& visitor) = 0;
private:
  std::string name;
  Element* parent;
};

class ElementEmpty : public Element{
public:
  ElementEmpty(std::string name, Element* parent = NULL) : Element(name, parent){}
  virtual void accept(Visitor& visitor);
};

class ElementLeaf : public Element{
public:
  ElementLeaf(std::string name, std::string value, Element* parent = NULL) : Element(name, parent), value{ value }{}
  virtual void accept(Visitor& visitor);
  std::string value;
};

class ElementComposite : public Element{
public:
  ElementComposite(std::string name, std::deque<std::string> value_names, Element* parent = NULL) : Element(name, parent){
    for(auto& vn : value_names){
      values.emplace_back(new ElementEmpty(vn, this));
    }
  }
  ~ElementComposite(){
    while(!values.empty()){
      delete values.back();
      values.pop_back();
    }
  }
  virtual void accept(Visitor& visitor);
  std::deque<Element*> values;
};

class Visitor{
public:
  virtual void visit(ElementEmpty& empty) = 0;
  virtual void visit(ElementLeaf& leaf) = 0;
  virtual void visit(ElementComposite& composite) = 0;
};


void ElementEmpty::accept(Visitor& visitor){
  visitor.visit(*this);
}

void ElementLeaf::accept(Visitor& visitor){
  visitor.visit(*this);
}

void ElementComposite::accept(Visitor& visitor){
  visitor.visit(*this);
}

class Printer : public Visitor{
public:
  virtual void visit(ElementEmpty& empty){
    std::cout << empty.get_name() << " is empty" << std::endl;
  }
  virtual void visit(ElementLeaf& leaf){
    std::cout << leaf.get_name() << " is a leaf with value" << leaf.value << std::endl;
  }
  virtual void visit(ElementComposite& composite){
    std::cout << composite.get_name() << " is composite, visiting childrens" << std::endl;
    for(auto& v : composite.values){
      v->accept(*this);
    }
  }
};

class Discover : public Visitor{
public:
  virtual void visit(ElementEmpty& empty){
    discover(&empty);
  }
  virtual void visit(ElementLeaf& leaf){
    discover(&leaf);
  }
  virtual void visit(ElementComposite& composite){
    discover(&composite);
    for(auto& v : composite.values){
      v->accept(*this);
    }
  }
private:
  void discover(Element* element){
    std::string url = element->get_url();
    //HTTP GET (pseudo-code here to avoid adding irelevant code)
    json_val value = http_get(url);
    Element* new_element;
    if(value.is_array()){
      new_element = new ElementComposite(element->get_name(), value.as_array().as_deque(), element->get_parent());
    }
    else if(value.is_object()){
      new_element = new ElementLeaf(element->get_name(), value.as_string(), element->get_parent());
    }
    else{
      new_element = new ElementEmpty(element->get_name(), element->get_parent());
    }

    //TODO: How to set element to new_element? it's impossible since we are currently in element->accept()....
  }

};

有什么方法可以在运行时替换以前分配的对象,而不必添加指向已创建对象的指针数组并在需要时更改它们?

谢谢!

【问题讨论】:

  • 在 C++ 中“替换”现有对象没有简单的方法。 C++ 不能以这种方式工作。唯一可行的方法是手动调用现有对象的析构函数,然后使用放置new,并确保提前分配足够的内存以容纳最大可能的“替换”对象。如果您不知道这意味着什么,则可能意味着您将无法根据 this 的所有规则正确实现它,这是当今使用的最复杂的通用编程语言。另一个选择可能是 C++ 的std::variant
  • 建议:生成一个minimal-reproducible-example 来隔离您的确切问题并提出相关问题。您现在提供的代码是 1% 物质,99% 噪音。即使你坚持重点,别人也更有可能理解你的问题。
  • @SamVarshavchik 我能想到的替换现有对象的唯一方法正是您所描述的。我是嵌入式 c 程序员和固件设计师,因此手动管理内存对我来说不是问题。问题是在我看来,这在很大程度上是一种 c 的做事方式......如果我找不到其他任何东西,我会回退到这个解决方案。我会看看 std::variant 看看它是否可以帮助我。谢谢!

标签: c++ rest oop design-patterns


【解决方案1】:

对于您在提供的代码中分配的所有元素,您可以通过调用 new 来动态分配。

对于 discover() 可能被调用的所有元素是否也是如此?

如果是这样,在某处调用的根,将是该元素的真正所有者。例如,改变 discover() 的接口怎么样,让它返回一个指向新元素的指针,一直沿着调用链向下到达旧元素的真正所有者?然后该所有者可以删除旧元素,并开始将指向新分配的元素的指针存储在其位置。

当然,通常 - 不要使用原始指针,使用 std::unique_ptr 或 std::shared_ptr 或其他任何合适的东西等。但上述代码转换仍然可以使用这些。

【讨论】:

  • 嗨!我刚刚按照您的建议测试了此方法,方法是添加一个带有返回 Element* 的访问函数的 ReturningVisitor 接口,并将此接口用作我的派生类 Discover 的基础。还将所需的接受方法添加到我的元素类中。它似乎工作得很好。我只测试了将 ElementEmpty 转换为 ElementLeaf,因为为 ElementComposite 添加必要的代码会更具挑战性,但它确实看起来会起作用。我会回来告诉你它是否运作良好并接受你的回答。谢谢!
  • 所以我在替换双端队列中的对象时遇到了一些内存泄漏,但我会弄清楚的。其他一切似乎都运行良好!再次感谢!
  • 太棒了!如前所述,如果您开始使用 std::unique_ptr 或 std::shared_ptr 而不是原始指针,则更容易避免意外引入内存泄漏。
  • 我现在正在研究 unique_ptr,它看起来适合我的需要。我在实现中必须做的唯一小改动是让 discover() 函数只返回 json 对象而不是 Element* 因为我必须根据发生的转换(如果有的话)做出不同的反应(例如: EmptyElement 到CompositeElement vs 完全没有变化 vs 等等...)。非常接近在实时 API 上对其进行测试! :)
猜你喜欢
  • 2014-01-22
  • 1970-01-01
  • 1970-01-01
  • 2015-02-26
  • 1970-01-01
  • 2013-12-31
  • 1970-01-01
  • 2016-04-02
  • 1970-01-01
相关资源
最近更新 更多