【问题标题】:C++ Visitor pattern with smart pointers带有智能指针的 C++ 访问者模式
【发布时间】:2016-09-29 08:38:32
【问题描述】:

我正在尝试在 C++ 中实现 Oppen's algorithm

此算法(打印和扫描)中的基本例程在令牌类型上分派。 使用访问者模式来实现这种调度似乎很自然。 问题是:例程是嵌套的,并且 print() 的参数在 scan() 期间被排入堆栈。 为了避免任何内存问题,我想对任务使用智能指针。

所以我的实现看起来像这样:

class Text;
class Line;
class Open;
class Close;

class Visitor {
  /* Define virtual visit functions for concrete doc nodes:
   */
public:
  virtual void visit(const Text&) = 0;  
  virtual void visit(const Line&) = 0;  
  virtual void visit(const Open&) = 0;  
  virtual void visit(const Close&) = 0; 
};


class DocToken
{
protected:
  explicit DocToken() {}

  friend class Visitor;

public:
  virtual void accept(Visitor * visitor) const = 0;
};

class Text : public DocToken {
public:
  Text(std::string s) : text(s) {} 
  void accept(Visitor *visitor) const {
    visitor -> visit (*this);
  }
  std::string text;
};

class Open : public DocToken { /* .. */ }

/* .. */

class Scan : public Visitor {
  stream_t stream;
  /* ... */
public:
  void visit(const Open& x) {
    /* ... */ 
    stream.push_back(/* .. */ new Open() /* .. */);
    /* ... */ 
  }

  void visit(const Text& x) {
    /* ... */ 
    stream.push_back(/* .. */ new Text(x) /* .. */);
    /* ... */ 
  }
  /* .. */
}

如您所见,Open 令牌不携带任何数据,并且可以轻松构建。 Text 标记确实携带数据(std::string)并且必须被复制才能被推送到流中。 由于 Open 和 Text 的通用抽象基类,流需要由指针组成。

由于在外部,有一个指向该文本标记的智能指针,我想避免复制并简单地使用现有的智能指针。 但是,accept 方法无权访问该智能指针。

有没有办法直接在智能指针上实现访问者模式?如果没有,如何降低复制文本令牌的成本?

【问题讨论】:

  • 你对Visitor的定义是什么? (另外,在您分析表明这是一个问题之前,我不会太担心复制)。
  • 我添加了实现。经典访客模式,AFAIK。是的,优化可能不值得,但如果是,我仍然不知道如何正确地做到这一点。所以我问;)
  • “由于在外部,有一个指向该文本标记的智能指针” 您是否将 每个 标记存储在 shared_ptr 中?即,您是否有一系列共享指针(指向基类),并且您将访问者全部传递给它们?
  • 是的,这就是我目前所做的。
  • 在什么情况下会在访问结构时破坏“外部”智能指针?在访问期间保持根目录而不是依次管理每个部分,似乎确实可以解决问题。

标签: c++ smart-pointers visitor-pattern


【解决方案1】:

从技术上讲,您可以使用std::enable_shared_from_this 执行此操作。 (请注意,Pete Kirkham 对该问题的出色评论 - 共享指针表示所有权。这适用于可能比其原始文档寿命更长的访问者,例如,临时词典构建器,它可能在文档关闭后仍然存在。如果没有涉及所有权,原始指针是要走的路。)

下面是说明这一点的代码的简化版本。

假设我们从通常的访问者模式前向声明和基类定义开始。

#include <memory>
#include <vector>
#include <iostream>

struct token;

struct visitor;

struct token {
    virtual void accept(visitor &v) = 0;
};

struct text_token;
struct open_token;

当我们定义visitor时,我们将其设为acceptstd::shared_ptrs的选项:

struct visitor {
    virtual void accept(std::shared_ptr<text_token> p) = 0;
    virtual void accept(std::shared_ptr<open_token> p) = 0;
};

现在,当我们制作具体的代币时,我们:

  1. 子类std::enable_shared_from_this
  2. 使用shared_from_this 将参数传递给accept

所以具体的记号变成:

struct text_token : public token, public std::enable_shared_from_this<text_token> {
    virtual void accept(visitor &v) override {
        std::shared_ptr<text_token> p{shared_from_this()};
        v.accept(p);
    }   
};

struct open_token : public token, public std::enable_shared_from_this<open_token> {
    virtual void accept(visitor &v) override {
        std::shared_ptr<open_token> p{shared_from_this()};
        v.accept(p);
    }   
};

具体的访问者变化不大:

struct scan : public visitor {
    virtual void accept(std::shared_ptr<text_token>) override {
        std::cout << "accepting text" << std::endl;
    }
    virtual void accept(std::shared_ptr<open_token>) override {
        std::cout << "accepting open" << std::endl;
    }   
};

现在我们可以定义std::shared_ptrs 到tokens 的范围

int main() {
    std::vector<std::shared_ptr<token>> toks;
    toks.push_back(std::make_shared<text_token>());
    toks.push_back(std::make_shared<open_token>());

然后给他们打电话accept

    scan s;
    for(auto p: toks)
       p->accept(s);
}

运行时会打印:

$ ./a.out 
accepting text
accepting open

完整代码

#include <memory>
#include <vector>
#include <iostream>

struct token;

struct visitor;

struct token {
    virtual void accept(visitor &v) = 0;
};

struct text_token;
struct open_token;

struct visitor {
    virtual void accept(std::shared_ptr<text_token> p) = 0;
    virtual void accept(std::shared_ptr<open_token> p) = 0;
};

struct text_token : public token, public std::enable_shared_from_this<text_token> {
    virtual void accept(visitor &v) override {
        std::shared_ptr<text_token> p{shared_from_this()};
        v.accept(p);
    }   
};

struct open_token : public token, public std::enable_shared_from_this<open_token> {
    virtual void accept(visitor &v) override {
        std::shared_ptr<open_token> p{shared_from_this()};
        v.accept(p);
    }   
};

struct scan : public visitor {
    virtual void accept(std::shared_ptr<text_token>) override {
        std::cout << "accepting text" << std::endl;
    }
    virtual void accept(std::shared_ptr<open_token>) override {
        std::cout << "accepting open" << std::endl;
    }   
};

int main() {
    std::vector<std::shared_ptr<token>> toks;
    toks.push_back(std::make_shared<text_token>());
    toks.push_back(std::make_shared<open_token>());

    scan s;
    for(auto p: toks)
       p->accept(s);
}

【讨论】:

  • 在您的main 函数中,为什么要将uniqe_ptr 推回shared_ptrs 的向量上?或者换一种说法,为什么不使用make_shared? (优点是 shared_ptr 控制块通常与实际对象在相同的内存分配中分配。)但除此之外,是的:这个!
  • @MartinBonner 感谢您的评论 - 非常感谢!心理打嗝。会更正。
猜你喜欢
  • 2013-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-30
  • 2019-05-22
  • 2021-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多