Стандартный способ обработки инкапсулированного доступа к значениям, хранящимся в приватной карте, без нарушения абстракции в C ++ - PullRequest
0 голосов
/ 21 января 2019

Я хочу создать класс для управления языком разметки (например, HTML) в C ++.Я хотел бы, чтобы мой класс сохранил атрибуты и под-теги.Проблема заключается в том, что, учитывая инкапсулированные контейнеры, как правильно абстрагировать доступ и что возвращать, чтобы обеспечить простой способ проверить, является ли возвращаемое значение допустимым.

Я определил свой класс, содержащий две карты, как частные члены(номинально 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?Если да, то как?Должен ли я пойти на более "С" подход, где я просто определяю тип возвращаемого значения Tag& и возвращаю NULL, если карта не содержит мой ключ?Должен ли я быть более ООП со статическим членом static const Tag NOT_FOUND и возвращать ссылку на этот объект, если элемент отсутствует на моей карте?Я также думал о создании исключения, но управление исключениями кажется довольно тяжелым и неэффективным в C ++.

1 Ответ

0 голосов
/ 21 января 2019

std::optional может помочь вам, но ему нужна стандартная библиотека, готовая к C ++ 17, поэтому вы можете также использовать boost::optional, что более или менее одинаково, поскольку дизайн AFAIK std::optional основан на буст один. (Поскольку повышение часто является источником новых стандартных предложений 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 class / struct в качестве класса значений, поэтому просто содержите std :: maps в качестве открытых членов. Поместите ваши функции синтаксического анализа в пространство имен вместе с контейнером Tag, и пусть они будут просто функциями или отдельными классами, если это необходимо.

Мое второе предложение небольшое: не используйте префикс _, оно зарезервировано и считается плохим стилем, но вы можете использовать его в качестве суффикса. Также не используйте директивы пространства имен вне блока класса / функции / пространства имен, то есть глобально, это плохой стиль в .cpp и очень плохой стиль в заголовке /.h/.hpp

Мое третье предложение: используйте фреймворк qi парсера Boost Spirit, вы сначала просто объявите свои классы значений, как я предлагаю, в то время как Qi автоматически заполнит их, используя Boost Fusion. Если вы уже знаете нотацию EBNF, вы можете просто написать EBNF как грамматику в C ++, и компилятор сгенерирует синтаксический анализатор с помощью шаблона. Однако у ци и особенно слияния есть некоторые проблемы, но в долгосрочной перспективе это значительно облегчает ситуацию. В лучшем случае регулярные выражения выполняют только половину логики синтаксического анализа.

...