【问题标题】:Returning a value from Visitor pattern从访客模式返回一个值
【发布时间】:2013-03-27 10:49:18
【问题描述】:

我的层次结构如下:

         class Element{ public : virtual void Accept(Visitor&) = 0
                 protected : Element();   int num;
          };

         class ElementA : public Element{
                 public : ElementA(); 
                 void Accept(Visitor& v) {v.Visit(this};}
         };

         class ElementB : public Element{
                 public : ElementB(); 
                 void Accept(Visitor& v) {v.Visit(this};}


         class Visitor{
                 public: void Visit(ElementA*);
                 void Visit(ElementB*);
         };

编辑: 需要将方法 int getNum() 添加到将提供 num 值的层次结构中。但是,这需要重新编译整个层次结构,我们不允许这样做。所以我们必须以某种方式改变层次结构的设计,以便不需要重新编译层次结构。

【问题讨论】:

  • 你打算如何从多个元素中返回一个值?或者你能接受不知道哪个值来自哪个元素吗?

标签: c++ design-patterns visitor-pattern


【解决方案1】:

您想要做的事情不可能以一种设计简洁的方式实现。我不知道,为什么完全重新编译该层次结构会是这样一个问题,但是有一个解决方案在技术上是可行的,而无需使用像reinterpret_casting 访问保护和其他黑客这样的 UB 黑客。

int Visitor::getNum(Element* pe)
{ 
  //define a pick-pocket... ;)
  struct ElementNumAccessor : private Element
  {
    ElementNumAccessor(Element const& e) : Element(e) {}   
    int getNum() { return num; }

    void Accept(Visitor&); //has to be declared, but needs not be defined
  };

  //...and let him work:
  ElementNumAccessor ea(*pe);
  return ea.getNum();
}

这个灵魂在行动:http://ideone.com/e1chSX
这利用了受保护的访问是可传递的这一事实,但以牺牲您想要 num 的每个元素的副本为代价。我将 struct 设为函数本地类,因此没有人会想到将其实际用于任何其他目的。

但请记住,这种技术是一种 hack,是对不应该以这种方式使用的语言功能的利用。

我的建议是:如果您的层次结构在程序中如此纠缠,以至于更改它会导致重新编译恐惧,那么是时候重构您的程序以减少编译时依赖性,然后进行您必须做的更改。

【讨论】:

  • 稍作改动 - 您不必复制元素
  • 嗯,可以吗?您正在访问不属于同一类的参数的受保护成员。我知道如果您将 ElemNumAccessor 作为输入,例如getNum(ElemNumAccessor),您的建议将起作用(因为访问限制是针对每个班级,而不是针对每个成员)。但是访问基类参数的成员不应该工作:stackoverflow.com/questions/7476117/…
  • 嗯,我会查一下 - 敬请期待
  • 当然。 :) 这个问题似乎仍然很可疑,但这是一个很好的解决方法。
【解决方案2】:

您的问题是如何允许类 (Visitor) 直接访问在不同类层次结构 (Elements) 中继承的私有数据成员。

一个快速的答案是:不要!这将破坏可见性修饰符的全部目的。 但是这样做是 still possible 使用 friend (导致重新编译层次结构)或通过指针重新解释。

一个干净的解决方案是使用adapter pattern(使用继承)将整个Elements 层次结构复制到另一个存根层次结构中,该存根层次结构提供与对应函数的委托完全相同的接口,为num 提供getter 函数并将新的层次结构与您的访问者一起使用,该访问者将能够通过 getter 访问 num

【讨论】:

  • 指针重新解释以访问私有成员是 UB - 阅读“纯粹的邪恶”,尽管它可以在许多系统中工作。与其他类成为朋友将是不必要的紧密耦合,并导致重新编译整个层次结构,这对 OP 来说是不行的。但幸运的是 num 不是私有的而是受保护的......
  • 我也认为使用指针重新解释不是一个好主意。但是我对你的最后一句话很感兴趣,在这种情况下使用 protected 而不是 private 有什么区别?
  • @Arne,据我了解,私有和受保护之间的主要区别在于后者使派生类可以访问基类。但是,如果您不允许修改类(即重新编译),这有什么用?还是我误解了那部分?
  • 查看我的答案 - 您可以从 Element 派生来创建访问其他元素的扒手 ;-)
  • 啊,这正是我在阅读您的评论后所想的!使用私有继承将整个层次结构调整为存根层次结构。投赞成票! :-)
【解决方案3】:

不是访问者模式方面的专家,但这符合您的要求吗?

    class Element{ public : virtual void Accept(Visitor&) = 0
             protected : Element();   int num;
      };

     class ElementA : public Element{
             public : ElementA(); 
             void Accept(Visitor& v) {v.setNum(this->num); v.Visit(this);}
     };

     class ElementB : public Element{
             public : ElementB(); 
             void Accept(Visitor& v) {v.setNum(this->num); v.Visit(this);}


     class Visitor{
             public:
             void Visit(ElementA*);
             void Visit(ElementB*);
             void setNum(int _num) {num = _num;}
             int getNum() {return num;}

             private:
             int num;
     };

【讨论】:

  • 不允许他重新编译Element*层次结构!
  • 对不起,出于某种原因,我认为修改界面是不允许的。在我的解决方案中,如果 Accept 函数在 .cpp 中实现,他可以不修改接口而侥幸,尽管你说得对,他必须重新编译。
  • 我遇到的问题是num 受到保护...您应该如何从访客访问它?要么你必须将 getNum 添加到 Element 类,要么你必须让 Visitor 成为 Element 的朋友——这两者都需要重新编译...
  • 我只能想到推荐的指针重新解释,并且可能会导致未定义的行为。所以也许答案是根本不可能。
【解决方案4】:

一种可能性是将GetNumVisitor 声明为Element class 的朋友并直接访问num 成员变量。然后在GetNumVisitor中添加一个方法返回值。

class Element { 
  ...
  friend GetNumVisitor;  // declare GetNumVisitor as a friend class
  ...
};

GetNumVisitor : public Visitor {
 private: 
  int  m_numelement;
 public : 
  void visit(Element *E) { m_elementNum = E->num; }
  int getNum() const { return m_elementNum;}
};

你必须把它称为

 ElementA element_a();
 ElementB element_b();
 GetNumVisitor getnumVisitor();
 element_a.accept(getnumVisitor);
 int a = getnumVisitor.getNum();
 element_b.accept(getNumVisitor);
 int b = getnumVisitor.getNum();
 ...

【讨论】:

  • 将 GetNumVisitor 声明为 Element 的朋友将导致重新编译 Element 并因此重新编译任何使用 Element 的类 - 意味着整个层次结构。您也可以向 Element 添加一个方法 getNum(),从技术上讲,它在编译依赖方面会产生相同的后果。
猜你喜欢
  • 1970-01-01
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
  • 2018-07-31
  • 2014-10-21
  • 2012-11-20
相关资源
最近更新 更多