В последние недели я снова и снова сталкиваюсь с одной и той же проблемой. В своей основе я строю (направленную ациклическую) иерархию объектов, таких как:
a -> c
b -> c
b -> d
Экземпляр может иметь более одного родителя и более одного потомка. c
может быть ценностью для читателей и писателей. b
может быть составным значением. Иерархия легко создается фабрикой — например, a.setChild(new C())
, Позже клиент заботится только о руте, по которому он звонит getValue()
или же setValue()
,
Мой вопрос: кто очищает иерархию? Кто отвечает за звонок delete
на b
или же c
?
Опция — фабрика создала узлы, фабрика должна удалить узлы:
Мне не нравится этот вариант, потому что я понимаю фабрику в качестве замены для new
, Странно держать фабрику до тех пор, пока созданные ею экземпляры не будут уничтожены.
Вариант — «Умные» указатели:
Гораздо менее хорошо, потому что это загрязняет интерфейс и вносит много сложностей для простых вещей, таких как указатели.
Опция — графоподобный класс, который управляет памятью:
Класс собирает все узлы a
, b
, c
, d
, … и предоставляет доступ к корню. Сами узлы ссылаются друг на друга, но не удаляют ни детей, ни родителей. Если «график» или составной менеджер уничтожается, он уничтожает все узлы.
Я предпочитаю последний вариант. Это, однако, усложняет построение иерархии. Либо вы строите иерархию снаружи и рассказываете графу о каждом отдельном узле, либо вы строите иерархию внутри графа, который пахнет как вариант 1. Граф может удалить только то, что ему известно. Поэтому утечки памяти как-то в дизайне, если вам нужно передать узлы на график.
Есть шаблон для этой проблемы? Какую стратегию вы предпочитаете?
Изменить 1 — 1 сентября 2014 г .: Извините, за неопределенность в отношении умных указателей. Я попытался избежать еще одного «вопроса об использовании умных указателей» и вместо этого сосредоточил внимание на альтернативных решениях. Тем не менее, я готов использовать умные указатели, если они действительно являются лучшим вариантом (или при необходимости).
На мой взгляд, подпись setChild(C* child)
должно быть предпочтительнее, чем setChild(std::shared_ptr<C> child)
по той же причине, что и для каждого цикла, следует отдавать предпочтение перед итераторами. Тем не менее, я должен признать, что, как std:string
разделяемый указатель более конкретен в отношении его семантики.
С точки зрения сложности, каждая операция внутри узла теперь должна иметь дело с общими указателями. std::vector<C*>
становится std::vector< std::shared_ptr<C> >
Кроме того, каждый указатель содержит счетчик ссылок, чего можно было бы избежать, если бы были другие варианты.
Я должен добавить, что я разрабатываю низкоуровневую часть системы реального времени. Это не прошивка, но близко.
Изменить 2 — 1 сентября 2014 г .:
Спасибо за весь вклад. Моя конкретная проблема: я получаю байтовый массив с, скажем, данными датчика. В какой-то момент мне говорят, какое значение записано где в этом массиве. С одной стороны, я хочу получить отображение от позиции в массиве до примитивного значения (int32, double, …). С другой стороны, я хочу объединить примитивные значения в сложные типы (структуры, векторы, …). К сожалению, не все отображения являются двунаправленными. Например. Я могу прочитать сравнение между значениями, но я не могу записать значения в соответствии с результатом сравнения. Поэтому я разделил читателей и писателей и позволил им, при необходимости, получить доступ к одному и тому же значению.
Вариант — «Умные» указатели: гораздо менее хороший, потому что он загрязняет
интерфейс, и вводит много сложности для простой вещи, такой
как указатели.
Умные указатели — это указатели с управлением памятью, которое именно то, что вам нужно.
Вы не обязаны выставлять умные указатели в своем интерфейсе, вы можете получить необработанный указатель от умного, пока объект все еще принадлежит умному указателю (обратите внимание, что это не всегда лучшая идея, умные указатели) в интерфейсе это далеко не страшно).
Фактическое предоставление необработанных указателей в вашем интерфейсе косвенно приводит к гораздо большему загрязнению и сложности, поскольку вам необходимо задокументировать правила владения и срока действия, которые являются явными при использовании интеллектуальных указателей, что делает их еще более простыми в использовании, чем «простые указатели».
Кроме того, это, скорее всего, «футурный» способ действий: поскольку умные указатели c ++ 11/14 являются частью стандарта std::string
скажешь что std::string
загрязняет интерфейс и вносит много сложности по сравнению с const char*
?
Если у вас есть проблемы с производительностью, тогда возникает вопрос: будет ли ваше альтернативное решение, созданное вручную, действительно более производительным (это нужно измерить), и стоит ли оно того, чтобы затратить время на разработку функции и поддержку кода?