Шаблон проектирования адаптеров и управление памятью в C ++ - PullRequest
4 голосов
/ 24 мая 2011

Рассмотрим следующую ситуацию: у меня есть модуль C ++, который принимает XML-узел в качестве входных данных, преобразует его в некоторую другую структуру данных и возвращает результат.

Сейчас этот модуль реализован с использованием TinyXML , поэтому он принимает классы TinyXML в качестве входных данных (в частности, TiXmlNode).Это проблема, потому что она заставляет любого, кто хочет использовать мой модуль, использовать TinyXML для представления всего дерева документа.Например, если пользователь RapidXML хочет использовать мой модуль, он не сможет, потому что модуль ожидает узел TinyXML (и последующие дочерние узлы), а не узел RapidXML.Очевидно, что это плохой дизайн из-за плохого повторного использования.

Чтобы решить эту проблему, я готов применить принцип обращения зависимостей .Поэтому я проектирую этот чрезвычайно упрощенный DOM-подобный интерфейс:

class Node
{
    public:
        enum Type { TYPE_ELEMENT, TYPE_TEXT };

        virtual ~Node() { }
        virtual Type getType() = 0;
        virtual Node* getParentNode() = 0;
        virtual Node* getPreviousSibling() = 0;
        virtual Node* getNextSibling() = 0;
};

class Element : public Node
{
    public:
        virtual ~Element() { }
        virtual const char* getName() = 0;
        virtual Node* getFirstChild() = 0;
        virtual Node* getLastChild() = 0;
        virtual const char* getAttribute(const char* name) = 0;
};

class Text : public Node
{
    public:
        virtual ~Text() { }
        virtual const char* getText() = 0;
};

Затем я реализую эти интерфейсы, используя шаблон проектирования Adapter , оборачивая эквивалентные классы TinyXML.Или, по крайней мере, я пытаюсь:

class AdapterNode : public Node
{
    private:
        const TiXmlNode& node;
    public:
        AdapterNode(const TiXmlNode& node) : node(node) { }
        virtual Node* getFirstChild() { Uh... oh, wait.

Вы можете видеть, что я направляюсь в кошмар управления памятью: я должен вернуть здесь уже существующий, уже выделенный Узел.И действительно, у меня есть уже существующий, уже выделенный TiXmlNode (доступный через node->FirstChild()), но это TiXmlNode, а не Node!Запись return new AdapterNode(node->FirstChild()) привела бы к явной утечке памяти, так как недавно выделенный AdapterNode никогда не получал бы delete 'd никем.

Несколько решений приходили мне в голову, чтобы решить эту проблему управления памятью, но большинствоиз них несколько некрасиво.Я прошу совета здесь: что бы вы предпочли в этой ситуации?

Ответы [ 3 ]

3 голосов
/ 24 мая 2011

Простое решение: используйте умный указатель. (умные указатели - это "простой" способ определения времени жизни объектов)

Например, здесь самым простым будет создать ваш объект с новым, а затем вернуть его в std :: shared_ptr (или boost :: shared_ptr).

Оптимизированное решение: используйте пулы объектов и предоставьте указатель или ссылочный тип, который гарантирует, что вы не можете удалить предоставленный объект.

Зависит от ваших данных: вернуть копию.

2 голосов
/ 25 мая 2011

Одним из наиболее заметных недостатков выбранного подхода является значительная нагрузка на разработчика на уровень абстракции.Я бы сказал, что это так же сложно, как превратить классы RapidXml в классы TinyXml, так что вы не получите ничего с этим новым слоем абстракции.Кстати, вы уже допустили ошибку, пытаясь вернуть 'new AdapterNode (node-> FirstChild ())' из метода Node :: getFirstChild: если первый дочерний элемент является элементом, вы не сможете преобразовать AdapterNode в AdapterElement.Это также приводит к снижению производительности и памяти, возможно, ограничивая возможность многократного использования, а также привязку к определенной библиотеке XML.

Я уже подумал о том, чтобы сделать сам Node (а также Element, Text и т. Д.) Умным указателемдля базовой реализации.Например:


class XmlApi;

class Node
{
public:
    void * getLowLevelInterface() const;

    XmlApi * getApi() const;

    ~Node();

    Node(Node const &);

    Node const & operator =(Node const &);

private:
    void * lowLevelInterface;
    XmlApi * api;
};

class Element : Node
{
};

class Text : Node
{
};

class XmlApi
{
public:
    // Node -> more specific interface conversions.
    virtual bool ToElement(Node const &, Element &) const = 0;
    virtual bool ToText(Node const &, Text &) const = 0;

    // Node memory management
    virtual void acquireNode(Node const &) const = 0;
    virtual void releaseNode(Node const &) const = 0;

    // Node API
    enum Type { TYPE_ELEMENT, TYPE_TEXT };

    virtual Type getType(Node const &) const = 0;
    virtual Node getParentNode(Node const &) const = 0;
    virtual Node getPreviousSibling(Node const &) const = 0;
    virtual Node getNextSibling(Node const &) const = 0;

    // Element API
    virtual char const * getName(Element const &) const = 0;
    virtual Node getFirstChild(Element const &) const = 0;
    virtual Node getLastChild(Element const &) const = 0;
    virtual char const * getAttribute(Element const &, char const *) const = 0;

    // etc.
};

Конечно, имеет смысл добавить встроенные оболочки для соответствующих методов от XmlApi до Node, Element и Text, но это лишь небольшая деталь реализации.

Главное преимущество здесьгораздо меньше накладных расходов (они все еще есть, например, преобразование строк в char const *) и создание реализации для конкретной библиотеки Xml гораздо проще.Вы просто распаковываете «низкоуровневый» указатель узла, передаете его «низкоуровневому» API и упаковываете результат.Нет (почти нет, см. Преобразование строк) управления памятью или сложной иерархии наследования.Реализации Node, Element и других классов являются фиксированными и должны быть написаны только один раз, и единственная изменяющаяся часть - XmlApi.

UPD: если вы можете сделать вашу библиотеку только заголовками с шаблонами, вы даже можете уничтожить все виртуальныеметоды вызывают накладные расходы, превращая XmlApi в параметр шаблона.

0 голосов
/ 25 мая 2011

Что вам нужно сделать, это создать сразу все дерево, владеть им из какого-либо главного объекта, а затем вернуть из него предварительно выделенные узлы.

XMLTree xml("xmlfilename.xml");
Node* ptr = xml.GetNode("beginning");

Кроме того, предпочитайте использование сильных типов и dynamic_cast некоторым функциям getType().

...