带有智能指针的 C++ 访问者模式
C++ Visitor pattern with smart pointers
我正在尝试用 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 token不携带任何数据,可以很容易地就地构建。文本令牌确实携带数据(std::string)并且必须被复制才能被推入流中。
由于 Open 和 Text 的公共抽象基 class,流需要由指针组成。
由于在外部有一个指向该文本标记的智能指针,我想避免复制并简单地使用现有的智能指针。
但是,accept 方法无法访问该智能指针。
有没有办法直接在智能指针上实现访问者模式?如果没有,如何降低复制文本令牌的成本?
从技术上讲,您可以使用 std::enable_shared_from_this
来完成此操作。 (不过请注意 Pete Kirkham 对这个问题的出色评论 - 共享指针表示所有权。这适用于可能比其原始文档更长寿的访问者,例如,一个临时字典生成器,它可能在文档关闭后仍然存在。如果没有涉及所有权,原始指针是可行的方法。)
下面是您的代码的简化版本,说明了这一点。
假设我们从通常的访问者模式前向声明和基础 class 定义开始。
#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
时,我们将其设为accept
std::shared_ptr
个选项:
struct visitor {
virtual void accept(std::shared_ptr<text_token> p) = 0;
virtual void accept(std::shared_ptr<open_token> p) = 0;
};
现在当我们制作具体的代币时,我们:
- subclass
std::enable_shared_from_this
- 使用
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_ptr
s到token
s
的范围
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);
}
我正在尝试用 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 token不携带任何数据,可以很容易地就地构建。文本令牌确实携带数据(std::string)并且必须被复制才能被推入流中。 由于 Open 和 Text 的公共抽象基 class,流需要由指针组成。
由于在外部有一个指向该文本标记的智能指针,我想避免复制并简单地使用现有的智能指针。 但是,accept 方法无法访问该智能指针。
有没有办法直接在智能指针上实现访问者模式?如果没有,如何降低复制文本令牌的成本?
从技术上讲,您可以使用 std::enable_shared_from_this
来完成此操作。 (不过请注意 Pete Kirkham 对这个问题的出色评论 - 共享指针表示所有权。这适用于可能比其原始文档更长寿的访问者,例如,一个临时字典生成器,它可能在文档关闭后仍然存在。如果没有涉及所有权,原始指针是可行的方法。)
下面是您的代码的简化版本,说明了这一点。
假设我们从通常的访问者模式前向声明和基础 class 定义开始。
#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
时,我们将其设为accept
std::shared_ptr
个选项:
struct visitor {
virtual void accept(std::shared_ptr<text_token> p) = 0;
virtual void accept(std::shared_ptr<open_token> p) = 0;
};
现在当我们制作具体的代币时,我们:
- subclass
std::enable_shared_from_this
- 使用
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_ptr
s到token
s
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);
}