处理对存储在私有映射中的值的封装访问的标准方法,而不破坏C++中的抽象

Standard way to handle the encapsulated access to values stored in private map without breaking the abstraction in C++

本文关键字:方法 抽象 C++ 标准 封装 存储 处理 映射 访问      更新时间:2023-10-16

我想创建一个类来管理C++中的标记语言(如HTML)。我希望我的类保留属性和子标记。问题是,对于封装的容器,如何正确地抽象访问以及返回什么,以便提供一种简单的方法来检查返回的值是否有效。

我将包含两个映射的类定义为私有成员(名义上是std::map<std::string, Tag> _children;std::map<std::string, std::string> _attr;。我定义了两个函数来填充这些字段,我想定义两个函数以读取访问存储的元素。

问题是,我不想打破我的抽象,因为我这样做是为了提高我的c++技能,我想找到正确的方法(或更干净的方法,或标准的方法)来做这件事。

一个基本的解决方案是简单地调用return map.find(s);,但随后我必须将函数的返回类型定义为std::map<std::string, Tag>::const_iterator,这将打破抽象。因此,我可以取消引用map.find()返回的迭代器,但如果值不在映射中,我将取消引用一个不可取消引用的迭代程序(_children.cend())。

到目前为止我定义的:

using namespace std;
class Tag {
static const regex re_get_name, re_get_attributes;
string _name;
map<string,string> _attr;
map<string,Tag> _children;
public:
Tag(const string &toParse) {
/* Parse line using the regex */
}
const string& name() const {
return _name;
}
Tag& add_child(const Tag& child) {
_children.insert(child._name, child);
return *this;
}
SOMETHING get_child(const string& name) const {
map<string,Tag>::const_iterator val = _children.find(name);
/* Do something here, but what ? */
return something;
}
SOMETHING attr(const string& name) const {
map<string, string>::const_iterator val = _attr.find(name);
/* Do something here, but what ? */
return something;
}
};
const regex Tag::re_get_name("^<([^\s]+)");
const regex Tag::re_get_attributes(" ([^\s]+) = "([^\s]+)"");

在C++中处理这种情况的正确方法是什么?我应该创建自己的Tag::const_iterator类型吗?如果是,如何?我应该采用更"C"的方法吗?在这种方法中,我只将返回类型定义为Tag&,如果映射不包含我的键,则返回NULL?如果静态成员static const Tag NOT_FOUND不在我的映射中,我是否应该更OOP,并返回对此对象的引用?我也想过抛出一个异常,但在C++中异常管理似乎相当繁重且无效。

std::optional可以帮助您,但需要一个支持C++17的标准库,因此同时您也可以使用或多或少相同的boost::optional,因为AFAIKstd::optional的设计是基于boost的。(由于boost通常是新C++标准提案的来源)

尽管由于你的方法普遍存在问题,我不愿意向你提出建议,但我仍然为你写了一个,但请考虑代码后的要点:

#include <string>
#include <regex>
#include <map>
#include <boost/optional.hpp>
class Tag {
static const std::regex re_get_name, re_get_attributes;
using string = std::string;
string _name;
std::map<string,string> _attr;
std::map<string,Tag> _children;
public:
Tag(const string &toParse) {
/* Parse line using the regex */
}
const string& name() const {
return _name;
}
Tag& add_child(const Tag& child) {
_children.emplace(child._name, child);
return *this;
}
boost::optional<Tag> get_child(const string& name) const {
auto val = _children.find(name);
return val == _children.cend() ? boost::optional<Tag>{} : boost::optional<Tag>{val->second};
}
boost::optional<string> attr(const string& name) const {
auto val = _attr.find(name);
return val == _attr.cend() ? boost::optional<string>{} : boost::optional<string>{val->second};
}
};

正如您所看到的,您基本上只是重新实现了std::map的容器语义,而且还使用了某种内置的解析器逻辑。我强烈反对这种方法,因为解析在匆忙中变得非常糟糕,并且将值生成代码混合到一个容器中(即应该用作值类)会使情况变得更糟。

我的第一个建议是将Tag类/结构声明/使用为值类,因此只将std::映射包含为公共成员。将解析函数与Tag容器一起放在命名空间中,如果需要,可以将它们作为函数或不同的类。

我的第二个建议很小:不要用_作为前缀,这是保留的,被认为是不好的风格,但你可以用它作为后缀。此外,不要在类/函数/命名空间块之外使用命名空间指令,即全局,.cpp中的风格不好,头/.h/.hpp 中的风格非常糟糕

我的第三个建议是:使用boost spirit qi解析器框架,您只需按照我的建议首先声明您的值类,而qi将通过boost融合自动填充它们。如果你已经知道EBNF表示法,你可以用C++编写类似EBNF的语法,编译器将通过模板魔术生成一个解析器。然而,气,尤其是融合有一些问题,但从长远来看,它会让事情变得容易得多。Regex最多只完成一半的解析逻辑。