处理对存储在私有映射中的值的封装访问的标准方法,而不破坏C++中的抽象
Standard way to handle the encapsulated access to values stored in private map without breaking the abstraction in C++
我想创建一个类来管理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最多只完成一半的解析逻辑。
- 如何在从抽象基派生的类中实现相同的方法?
- 让编译器告诉什么确切的纯虚拟方法使结构抽象?
- 对纯抽象类中方法的未定义引用
- 用c++中的纯虚拟方法抽象模板类
- 我们可以在没有新实例化的情况下声明一个抽象方法来返回抽象超类中的子类对象吗
- 有没有一种方法可以使用SFINAE来检测一个类型是否实现了给定的抽象基类
- 工厂方法模式使用继承而抽象工厂模式使用组合如何
- C++ 多重继承:使用基类 A 的实现实现基类 B 的抽象方法
- c++ 中的抽象方法是否曾经调用过?
- 无法实例化抽象类,但类不是抽象/派生方法的参数
- 在 c++ 中抽象两个略有不同的函数的最佳方法是什么?
- 抽象类/接口中的空方法是否被认为是一种好的做法?
- 如何使用抽象类中未包含的方法
- 抽象包装带有异常的 C 错误处理的最佳方法
- 确保派生的类实现至少是来自抽象类(C )的两种方法之一
- C :如何在此抽象类的方法中返回抽象类
- 我将如何通过抽象类传递虚拟方法,但可以选择不重写
- 从子类中的抽象类调用方法
- 我们可以通过在C++中的类中使用关键字“abstract”使所有方法抽象来声明接口类吗
- 调试数据方法 Q抽象表模型