在不破坏C+中的抽象的情况下,处理对私有映射中存储的值的封装访问的标准方法+; 我想创建一个类来管理C++中的标记语言(如HTML)。我希望我的类保留属性和子标记。问题是,给定封装的容器,如何正确地抽象访问以及返回什么,以便提供一种简单的方法来检查返回的值是否有效

在不破坏C+中的抽象的情况下,处理对私有映射中存储的值的封装访问的标准方法+; 我想创建一个类来管理C++中的标记语言(如HTML)。我希望我的类保留属性和子标记。问题是,给定封装的容器,如何正确地抽象访问以及返回什么,以便提供一种简单的方法来检查返回的值是否有效,c++,containers,return-value,encapsulation,C++,Containers,Return Value,Encapsulation,我将包含两个映射的类定义为私有成员(名义上是std::map\u children;和std::map\u attr;) 问题是,我不想打破我的抽象,我这样做是为了处理我的C++技能,我想找到合适的方法(或者更干净的方法,或者标准的方法)。 一个基本的解决方案是简单地调用returnmap.find(s);,但是我必须将我的函数的返回类型定义为std::map::const_迭代器,这将破坏抽象。因此我可以取消引用map.find()返回的迭代器,但如果映射中没有中的值,我将取消引用不可取消引用

我将包含两个映射的类定义为私有成员(名义上是
std::map\u children;
std::map\u attr;

问题是,我不想打破我的抽象,我这样做是为了处理我的C++技能,我想找到合适的方法(或者更干净的方法,或者标准的方法)。 一个基本的解决方案是简单地调用
returnmap.find(s);
,但是我必须将我的函数的返回类型定义为
std::map::const_迭代器
,这将破坏抽象。因此我可以取消引用
map.find()返回的迭代器
,但如果映射中没有中的值,我将取消引用不可取消引用的迭代器(
\u 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]+)\"");

使用名称空间std;
类标签{
静态常量regex re_get_name、re_get_属性;
字符串\u名称;
地图属性;
地图儿童;
公众:
标记(常量字符串和toParse){
/*使用正则表达式解析行*/
}
常量字符串&name()常量{
返回_name;
}
标记和添加子项(常量标记和子项){
_儿童。插入(儿童姓名,儿童);
归还*这个;
}
SOMETHING get_child(const字符串和名称)const{
map::const_迭代器val=_children.find(name);
/*在这里做点什么,但是什么*/
归还某物;
}
属性(常量字符串和名称)常量{
map::const_iterator val=_attr.find(name);
/*在这里做点什么,但是什么*/
归还某物;
}
};

const regex Tag::re_get_name(“^
std::optional
可以帮助您,但需要一个C++17就绪的标准库,因此在此期间您还可以使用大致相同的
boost::optional
,因为AFAIK
std::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::regex re_get_name,re_get_属性;
使用string=std::string;
字符串\u名称;
标准::地图_属性;
性病:地图儿童;
公众:
标记(常量字符串和toParse){
/*使用正则表达式解析行*/
}
常量字符串&name()常量{
返回_name;
}
标记和添加子项(常量标记和子项){
_儿童就业(儿童姓名,儿童);
归还*这个;
}
boost::可选的get_child(const字符串和名称)const{
auto val=_children.find(name);
return val==_children.cend()?boost::optional{}:boost::optional{val->second};
}
boost::可选属性(常量字符串和名称)常量{
自动值=_attr.find(名称);
return val==_attr.cend()?boost::optional{}:boost::optional{val->second};
}
};
正如您所看到的,您基本上只是重新实现了
std::map
的容器语义,但也使用了某种内置的解析器逻辑。我强烈反对这种方法,因为解析很快就会变得难看,并且将值生成代码混合到一个可以(即应该)用作值类的容器中会使事情变得复杂更糟的是

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

我的第二个建议很小:不要用
\uuu
作为前缀,它是保留的,被认为是不好的样式,但可以将它用作后缀。也不要在类/函数/命名空间块(即全局)之外使用命名空间指令,它在.cpp中是不好的样式,在头/h/.hpp中是极坏的样式


我的第三个建议:使用Boost精神Qi-分析器框架,首先你将声明你的值类,然后通过Boost融合自动填充它们。如果你已经知道EBNF符号,你可以在C++中写EBNF类语法,编译器会通过模板魔术生成解析器。特别是融合有一些问题,但从长远来看,它使事情变得更容易。正则表达式最多只能完成一半的解析逻辑。

看看
std::option
。也许我误解了这个问题,但为什么不直接返回
map::const_迭代器呢?它是
const
迭代器,所以没有封装broken@molbdnilo
std::可选guess@user463035818在本例中,是抽象被破坏了。如果我想更改实现而不再使用映射,那么返回类型将更改。@user463035818返回的类型是什么?
可选
?我想对于将接收的变量,返回的类型将是相同的
Tag::get_child
的返回值。您可以提供一个
迭代器
typedef并使用该类型的迭代器,如果用户使用别名,当您将映射更改为其他内容时,他们的代码将不会中断。嗯,不确定,我必须承认我从未获得过什么enc