Однако, есть ньюансы, связанные со спецификой shared_ptr, а точнее внешним хранением блока счетчиков ссылок по отношению к объекту. Нельзя создавать более одного shared_ptr по сырому указателю Node*.
Хотелось бы узнать, как принято работать с shared_ptr в таких случаях?
Варианты, которые приходят в голову следующие:
1) следить за руками, что бы ненароком не написали сырой Node* и везде принимать/передавать shared_ptr<Node>
2) инкапсулировать указатели на хранимые в shared_ptr данные.. Например, так:
Здесь узел является оберткой над shared_ptr указателем на свои данные. Т.е. фактически сырые указатели спрятаны от использующего кода и при корректной реализации испортить извне что-то будет сложно. Однако, меняется семантика, полиморфизм сильно усложняется.
Вместе с тем доступны другие подходы по организации дерева. Например, интрузивный счетчик ссылок аки в COM, позволяет смешивать в разумных пределах работу с сырыми указателями и смартпоинтерами за счет того что счетчик жестко привязан к объекту. Однако время жизни счетчика и самого объекта связаны, потому невозможен подход со слабыми указателями, а значит либо циклические ссылки в дереве, где нужно знать родителя (если указатель на родителя умный), либо проблема устаревания указателя (если указатель глупый).
Еще один возможный подход — привязка блока счетчиков к указываемому объекту:
class RefCounted
{
struct Counter
{
int strong_counter;
int weak_counter;
RefCounted* ptr;
}
private:
Counter *c;
}
template <class T> strong_ptr
{
T* ptr; // указывает на сам объект
}
template <class T> weak_ptr
{
RefCounted::Counter* ptr; // указывает на блок счетчиков
}
class Node: public RefCounted
{
vector< strong_ptr<Node> > children;
weak_ptr<Node> parent;
}
Здесь по сырому указателю можно получить адрес блока счетчиков, потому создание неограниченного количества смартпоинтеров по сырому указателю не возбраняется. Время жизни объекта и блока счетчиков разнесено, потому возможно использования слабых указателей, не блокирующих удаление объекта.
По сравнению с shared_ptr мне в последнем варианте нравится то, что работая с Node* уменьшается косвенность, при этом остается возможность усилить сырой указатель до умного в любой момент.
Меня интересует мнения практиков C++ по поводу вышеописанного. Как принято, как бы вы поступили в своем проекте?
И еще: верно ли я понимаю, что смартпоинтеры с управлением жизни объекта не совместимы с константностью?
Здравствуйте, samius, Вы писали:
S>Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу: S>
Здравствуйте, samius, Вы писали:
S>Доброе время суток! S>Помогите, пожалуйста, определиться:
S>Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу: S>
Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе.
vector< shared_ptr<Node> > — фейлдизайн.
Здравствуйте, remark, Вы писали:
R>KISS. Когда изымаешь узел из дерева, удаляешь его. Всё.
Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.
R>
Здравствуйте, Kluev, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Доброе время суток! S>>Помогите, пожалуйста, определиться:
K>Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе.
Как уже ответил выше — дотнет ГМ. Что интересно — нет желания приучаться следить за владением объектов.
K>vector< shared_ptr<Node> > — фейлдизайн.
можно развернуть тезис?
Здравствуйте, samius, Вы писали:
R>>KISS. Когда изымаешь узел из дерева, удаляешь его. Всё.
S>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.
Их в принципе не получится шарить — parent и набор дочерних узлов один. Назначение этого узла в том и есть, что бы быть одним узлом одного дерева. Поэтому создал, добавил, изъял, удалил. Всё очень просто.
Пользовательские же данные — это другое дело. Пользователь может захотеть положить один объект в несколько деревьев. Но это пусть он думает, что передать в качестве T, может int, а может shared_ptr<vector<string>>.
Здравствуйте, samius, Вы писали:
K>>Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе. S>Как уже ответил выше — дотнет ГМ. Что интересно — нет желания приучаться следить за владением объектов.
Понимать, как работает система, и что в ней в действительности происходит, никогда не помешает. А выбор наиболее оптимального (как по простоте, так и по производительности) решения — это уже следствие.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, samius, Вы писали:
R>>>KISS. Когда изымаешь узел из дерева, удаляешь его. Всё.
S>>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.
R>Их в принципе не получится шарить — parent и набор дочерних узлов один. Назначение этого узла в том и есть, что бы быть одним узлом одного дерева. Поэтому создал, добавил, изъял, удалил. Всё очень просто.
Нет, шарить не в том смысле что один узел находится в нескольких деревьях, а в том, что разные объекты программы держат указатель на разные узлы дерева. И не хотелось заботиться, кто из этих объектов главный и отвечает за уничтожение дерева с его детьми.
R>Пользовательские же данные — это другое дело. Пользователь может захотеть положить один объект в несколько деревьев. Но это пусть он думает, что передать в качестве T, может int, а может shared_ptr<vector<string>>.
согласен, но я не уточнил, что речь идет не об обобщенном Node<T>, а о конкретном, например XmlNode. R>
Здравствуйте, Kluev, Вы писали:
K>Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе. K>vector< shared_ptr<Node> > — фейлдизайн.
а что по поводу vector< scoped_ptr<Node> >?
Вобще зачем удалять что-то в деструкторе, если можно скомбинировать типы и не удалять что-то в деструкторе? В конце концов C++ а не C.
Здравствуйте, samius, Вы писали:
S>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.
Если ты планируешь шарить поддеревья, то подумай о том, указатель на кого их родителей ты хочешь иметь в поддереве?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, samius, Вы писали:
S>>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.
E>Если ты планируешь шарить поддеревья, то подумай о том, указатель на кого их родителей ты хочешь иметь в поддереве?
речь была о том чтобы шарить ответственность за освобожденние, а не узлы между деревьями. Выше уже поправился.
Здравствуйте, samius, Вы писали:
R>>Их в принципе не получится шарить — parent и набор дочерних узлов один. Назначение этого узла в том и есть, что бы быть одним узлом одного дерева. Поэтому создал, добавил, изъял, удалил. Всё очень просто. S>Нет, шарить не в том смысле что один узел находится в нескольких деревьях, а в том, что разные объекты программы держат указатель на разные узлы дерева. И не хотелось заботиться, кто из этих объектов главный и отвечает за уничтожение дерева с его детьми.
А это и не надо.
Вполне достаточно, чтобы родитель, в своём деструкторе, попросил детей забыть о себе...
Я бы, либо, заюзал intrisive_ptr, либо свой. Для хранения ссылки на ноду. Как из отца, так и из клиентского кода.
И просто голый указатель, для хранения в ноде указателя на родителя.
+ Когда ноду усыновляют, в неё прописывают родителя, а когда родитель умирает, он отписывается от своих нод, и делает им Release, ну, в смысле, разрушает свои коппии intrisive_ptr...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, samius, Вы писали:
S>>>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.
E>>Если ты планируешь шарить поддеревья, то подумай о том, указатель на кого их родителей ты хочешь иметь в поддереве?
S>речь была о том чтобы шарить ответственность за освобожденние, а не узлы между деревьями. Выше уже поправился.
А чего тут шарить ответственность? В конец remove() добавляешь free(), кто вызвал remove(), тот вызовет и free(). О каком разделении ответственности идёт речь?
Здравствуйте, remark, Вы писали:
R>Здравствуйте, samius, Вы писали:
S>>речь была о том чтобы шарить ответственность за освобожденние, а не узлы между деревьями. Выше уже поправился.
R>А чего тут шарить ответственность? В конец remove() добавляешь free(), кто вызвал remove(), тот вызовет и free(). О каком разделении ответственности идёт речь?
допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.
R>
Здравствуйте, samius, Вы писали:
S>допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.
А ты хочешь, чтобы так было, или ты хочешь, чтобы так не было?
Потому, как хранить можно только подветку, вынув её из дерева, и забыв о родителях.
А ещё можно фейкового родителя иметь, кстати.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, samius, Вы писали:
S>>допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.
E>А ты хочешь, чтобы так было, или ты хочешь, чтобы так не было?
Хочу что бы не пришлось следить. Только тут нужно разобраться, что вреднее, не следить или хотеть.
E>Потому, как хранить можно только подветку, вынув её из дерева, и забыв о родителях.
В случае shared_ptr можно хранить и дерево и ветки как в сборе, так и отдельно вполне прозрачно. В том и прелесть.
Здравствуйте, samius, Вы писали:
S>Доброе время суток! S>Помогите, пожалуйста, определиться:
S>Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу: S>
S>Однако, есть ньюансы, связанные со спецификой shared_ptr, а точнее внешним хранением блока счетчиков ссылок по отношению к объекту. Нельзя создавать более одного shared_ptr по сырому указателю Node*.
если вы перешли на смарт-пойнтеры, то должны уйти от сырых указателей и этого нюанса не будет в принципе
S>Меня интересует мнения практиков C++ по поводу вышеописанного. Как принято, как бы вы поступили в своем проекте?
универсальных деревьев нет, нужно смотреть специфику задачи.
если цель сделать наиболее корректное и менее подверженное ошибкам при использовании, то полностью на смарт-пойнтерах (с ручным разбитием циклов weak_ptr в помощь) (нужно четко представлять кто имеет сильную ссылку, а кому дать слабую)
если важна производительность, то на сырых указателях с ручным выделением\освобождением ресурсов (главное сформулировать четкие правила кто и когда должен освобождать ресурс)
можно однонаправленное дерево сделать (ноды не знают своего родителя), чтобы облегчить себе жизнь. для обхода можно итераторы использовать
S>И еще: верно ли я понимаю, что смартпоинтеры с управлением жизни объекта не совместимы с константностью?
не очень понятен вопрос. shared_ptr<const MyClass> вполне можно делать)
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, samius, Вы писали:
S>>Меня интересует мнения практиков C++ по поводу вышеописанного. Как принято, как бы вы поступили в своем проекте? U>универсальных деревьев нет, нужно смотреть специфику задачи.
нет, речь не об универсальном дереве. Узел есть нечто вроде узла дерева XML. Пока они однотипны, но сейчас еще не видно, будет ли специализация.
U>если цель сделать наиболее корректное и менее подверженное ошибкам при использовании, то полностью на смарт-пойнтерах (с ручным разбитием циклов weak_ptr в помощь) (нужно четко представлять кто имеет сильную ссылку, а кому дать слабую)
цель сделать пока удобное в использовании дерево. где втыкать слабую — я представляю.
U>если важна производительность, то на сырых указателях с ручным выделением\освобождением ресурсов (главное сформулировать четкие правила кто и когда должен освобождать ресурс)
На этом этапе хочется избежать ручной реализации освобождения.
U>можно однонаправленное дерево сделать (ноды не знают своего родителя), чтобы облегчить себе жизнь. для обхода можно итераторы использовать
не в моем случае. родители должны быть достижимы из детей без наличия указателя на рута дерева.
S>>И еще: верно ли я понимаю, что смартпоинтеры с управлением жизни объекта не совместимы с константностью? U>не очень понятен вопрос. shared_ptr<const MyClass> вполне можно делать)
Разве удаление объекта не нарушает его константность?
А сконвертировать shared_ptr<MyClass> в shard_ptr<const MyClass>?
Здравствуйте, samius, Вы писали:
E>>Потому, как хранить можно только подветку, вынув её из дерева, и забыв о родителях. S>В случае shared_ptr можно хранить и дерево и ветки как в сборе, так и отдельно вполне прозрачно. В том и прелесть.
На самом деле не всё так радужно. GC сделать трудно. Зато не нужно.
Можно пойти по другому пути. Можно сделать свой указатель на ноду, который будет хранить счётчик сразу на дерево. Но я подозреваю, что это не то, что нужно, на самом-то деле.
Обычно удобно, когда ветка дерева может жить обособлено. Но это уже от твоей задачи зависит, на самом деле.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, samius, Вы писали:
E>>>Потому, как хранить можно только подветку, вынув её из дерева, и забыв о родителях. S>>В случае shared_ptr можно хранить и дерево и ветки как в сборе, так и отдельно вполне прозрачно. В том и прелесть.
E>На самом деле не всё так радужно. GC сделать трудно. Зато не нужно. E>Можно пойти по другому пути. Можно сделать свой указатель на ноду, который будет хранить счётчик сразу на дерево. Но я подозреваю, что это не то, что нужно, на самом-то деле.
E>Обычно удобно, когда ветка дерева может жить обособлено. Но это уже от твоей задачи зависит, на самом деле.
Задача — построение задачеориентированной объектной модели для обработки XML. В частности из XML дерево и получается, но потом обрастает спецификой задачи, из атрибутов вырастают свойства, появляеются спецметоды обработки и т.п.
Смущает меня то, что если мы добавляем к дереву А ветку Б, то получим дерево. Если мы от дерева отнимем ветку Б, то мы ее уже не сможем приклеить к дереву В, т.к. она будет тут же удалена. Как-то не хочется различать операции удаления поддерева от отцепления поддерева.
Хочется что бы отодрал ветку — она жива. Бросил — уничтожилась.
Здравствуйте, samius, Вы писали:
S>Разве удаление объекта не нарушает его константность?
тут дело в не в смарт-пойнтерах. а в языке
удалять объект разрешено, если есть указатель на конст объект
S>нет, речь не об универсальном дереве. Узел есть нечто вроде узла дерева XML. Пока они однотипны, но сейчас еще не видно, будет ли специализация. S>На этом этапе хочется избежать ручной реализации освобождения.
Ввести сущность контейнер-дерева, который содержит в себе узлы. Это дополнительно помогает оптимизировать память. Можно считать контейнер за пул узлов. Каждый узел имеет ссылку на контейнер (или можно вычислить адрес контейнера, если это линейный кусок памяти). Сам контейнер имеет счётчик ссылок и соответственно адресация к узлам идёт через указатель с подсчётом ссылок, в котором пользуется счётчик контейнера.
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, samius, Вы писали:
S>>Разве удаление объекта не нарушает его константность? U>тут дело в не в смарт-пойнтерах. а в языке U>удалять объект разрешено, если есть указатель на конст объект U>
U>const A* const ptr = new A();
U>delete ptr;
U>
По меньшей мере странно это выглядит. Изменять нельзя, удалять можно.
S>>А сконвертировать shared_ptr<MyClass> в shard_ptr<const MyClass>? U>сконвертить можно простым присваиванием вроде U>можно и убирать конст (аналог const_cast для shared_ptr) U>http://www.boost.org/doc/libs/1_41_0/libs/smart_ptr/shared_ptr.htm#const%5Fpointer%5Fcast
спасибо за ссылку, не натыкался на такой метод.
Здравствуйте, samius, Вы писали:
S>Хочется что бы отодрал ветку — она жива. Бросил — уничтожилась.
Ну, то есть, ветки могут жить без дерева/без родителя? Я верно понял?
ну тогда делаешь просто.
1) На ноду в клиентском коде всегда указываешь через shared_ptr
2) Нода на своих детей тоже через shared_ptr указывает
3) Дети хранят в себе обычный указатель на родителя.
4) При добавлении подветки (в том числе и одинокой ноды) в дерево, в смысле в список детей какого-то родителя, родитель регится в своём новом ребёнке.
При отделении ребёнка в свободное плавание -- родитель разрегистрируется в ребёнке. И ребёнок становится сиротой.
А в деструкторе MyTree каждому детю из children скажут setParent( 0 );\
Теперь то, что касается "исчезнет, как только я его оттуда достану". Если ты всё время будешь иметь shared_ptr на ноду, то она не исчезнет. Просто может осиротеть.
Ну и последнее. Я не знаю насколько большие деревья предполагаются, но если реально большие, то меня бы на shared_ptr задавила бы жаба. Можно же раздельнео владение со счётчиком и подешевле организовать!
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Alexey Sudachen, Вы писали:
S>>нет, речь не об универсальном дереве. Узел есть нечто вроде узла дерева XML. Пока они однотипны, но сейчас еще не видно, будет ли специализация. S>>На этом этапе хочется избежать ручной реализации освобождения.
AS>Ввести сущность контейнер-дерева, который содержит в себе узлы. Это дополнительно помогает оптимизировать память. Можно считать контейнер за пул узлов. Каждый узел имеет ссылку на контейнер (или можно вычислить адрес контейнера, если это линейный кусок памяти). Сам контейнер имеет счётчик ссылок и соответственно адресация к узлам идёт через указатель с подсчётом ссылок, в котором пользуется счётчик контейнера.
До знакомства с дотнетом мне бы такое решение понравилось
Здравствуйте, samius, Вы писали:
S>допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.
А, понятно, что ты делаешь.
Я бы наверное использовал интрузивные счётчики "в стиле COM", а проблему слабого указателя на родителя решил бы тем, что родитель обнуляет указатель на себя у всех дочерних узлов (у него все равно есть этот список). Что-то типа такого:
struct node_t
{
unsigned refcount_;
node_t* parent_;
std::vector<node_t*> children_;
int data_;
node_t(int data)
{
data_ = data;
refcount_ = 1;
parent_ = 0;
}
node_t* acquire()
{
++refcount_;
return this;
}
void release()
{
if (--refcount_ == 0)
delete this;
}
void add_child(node_t* n)
{
assert(n->parent_ == 0);
n->acquire();
n->parent_ = this;
children_.push_back(n);
}
~node_t()
{
assert(parent_ == 0);
for (auto child = children_.begin(); child != children_.end(); ++child)
{
assert((*child)->parent_ == this);
(*child)->parent_ = 0;
(*child)->release();
}
}
};
int main()
{
node_t* n1 = new node_t (1);
node_t* n2 = new node_t (2);
node_t* n3 = new node_t (3);
// собираем дерево
n1->add_child(n2);
n2->release();
n1->add_child(n3);
n3->release();
// захватываем один из подузлов
node_t* n4 = n2->acquire();
// удаляем дерево, но наш узел пока живёт
n1->release();
// отпускаем наш подузел
n4->release();
}
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, samius, Вы писали:
S>>Хочется что бы отодрал ветку — она жива. Бросил — уничтожилась.
E>Ну, то есть, ветки могут жить без дерева/без родителя? Я верно понял?
да
E>ну тогда делаешь просто.
E>1) На ноду в клиентском коде всегда указываешь через shared_ptr E>2) Нода на своих детей тоже через shared_ptr указывает E>3) Дети хранят в себе обычный указатель на родителя. E>4) При добавлении подветки (в том числе и одинокой ноды) в дерево, в смысле в список детей какого-то родителя, родитель регится в своём новом ребёнке. E>При отделении ребёнка в свободное плавание -- родитель разрегистрируется в ребёнке. И ребёнок становится сиротой.
да, что-то подобное я подразумевал в стартовом посте.
E>то есть код метода AddChild будет какой-то такой:
да, но предпочитаю передавать shared_ptr по константной ссылке, чтобы уменьшить передергивания счетчика.
E>Теперь то, что касается "исчезнет, как только я его оттуда достану". Если ты всё время будешь иметь shared_ptr на ноду, то она не исчезнет. Просто может осиротеть.
да, это я представляю.
E>Ну и последнее. Я не знаю насколько большие деревья предполагаются, но если реально большие, то меня бы на shared_ptr задавила бы жаба. Можно же раздельнео владение со счётчиком и подешевле организовать!
нет, реально не очень большие, жаба пока не давит.
Здравствуйте, samius, Вы писали:
S>да S>да, что-то подобное я подразумевал в стартовом посте. S>да, но предпочитаю передавать shared_ptr по константной ссылке, чтобы уменьшить передергивания счетчика. S>да, это я представляю. S>нет, реально не очень большие, жаба пока не давит.
Тогда не ясно в чём твои проблемы-то?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, remark, Вы писали:
R>Здравствуйте, samius, Вы писали:
S>>допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.
R>А, понятно, что ты делаешь. R>Я бы наверное использовал интрузивные счётчики "в стиле COM", а проблему слабого указателя на родителя решил бы тем, что родитель обнуляет указатель на себя у всех дочерних узлов (у него все равно есть этот список).
Да, к интрузивным счетчикам я уже пришел, но ввожу их через базовый класс и таки указатель на объекты с интрузивынми счетчиками сделал умными.
А вот решение проблемы слабого родителя через удаление ссылки родителем мне показалось интереснее, чем впендюривание weak_ptr в интрузивный подсчет ссылок.
R>Что-то типа такого:
+1, только по-ближе к C++, чем к C.
R>
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, samius, Вы писали:
S>>да S>>да, что-то подобное я подразумевал в стартовом посте. S>>да, но предпочитаю передавать shared_ptr по константной ссылке, чтобы уменьшить передергивания счетчика. S>>да, это я представляю. S>>нет, реально не очень большие, жаба пока не давит.
E>Тогда не ясно в чём твои проблемы-то?
проблемы в потенциальной опасности нарушения целостности памяти путем случайного приведения shared_ptr<T> к T* и обратно. Дисциплина и порядок — это решение этой проблемы. Но оно не надежное. Т.е. решение надежное, но вот дисциплина у меня хромает. Рассеянный. Хочу что бы проблема не возникала в принципе, либо хоть компилятор бы принуждал к дисциплине. А он молчит
Здравствуйте, samius, Вы писали:
S>>>Разве удаление объекта не нарушает его константность? U>>тут дело в не в смарт-пойнтерах. а в языке U>>удалять объект разрешено, если есть указатель на конст объект U>>
U>>const A* const ptr = new A();
U>>delete ptr;
U>>
S>По меньшей мере странно это выглядит. Изменять нельзя, удалять можно.
Что же тут странного? Гарантируется, что в течение свой жизни const-экземпляр будет неизменным (ну, не считая хаков со всякими кастами-мутастами). Но также верно, что каждый порожденный экземпляр имеет конституционное право на смерть. Тут и сказке конец.
Здравствуйте, Mr.Delphist, Вы писали:
MD>Здравствуйте, samius, Вы писали:
U>>>удалять объект разрешено, если есть указатель на конст объект
S>>По меньшей мере странно это выглядит. Изменять нельзя, удалять можно.
MD>Что же тут странного? Гарантируется, что в течение свой жизни const-экземпляр будет неизменным (ну, не считая хаков со всякими кастами-мутастами).
Если только считать что он умирает мгновенно не изменившись. Но из деструктора можно вызвать любой неконстантный метод, что делает смерть объекта не атомарной.
MD>Но также верно, что каждый порожденный экземпляр имеет конституционное право на смерть. Тут и сказке конец.
Странно что в этой сказке вершить приговор может любой.
S>Однако, есть ньюансы, связанные со спецификой shared_ptr, а точнее внешним хранением блока счетчиков ссылок по отношению к объекту. Нельзя создавать более одного shared_ptr по сырому указателю Node*.
S>Хотелось бы узнать, как принято работать с shared_ptr в таких случаях?
intrusive_ptr — и экономнее и проблему данную решает..
Как много веселых ребят, и все делают велосипед...
Здравствуйте, remark, Вы писали:
R>Я бы наверное использовал интрузивные счётчики "в стиле COM", а проблему слабого указателя на родителя решил бы тем, что родитель обнуляет указатель на себя у всех дочерних узлов (у него все равно есть этот список). Что-то типа такого:
...
только вектор тут ни к чему, я бы как нибудь так сделал:
struct node_t : intrusive_list_member<> // или какой нибудь intrusive_set если надо по детям поиск прикручивать
{
node_t* parent_;
intrusive_list<node_t> children_;
Здравствуйте, remark, Вы писали:
R>Здравствуйте, sokel, Вы писали:
S>>только вектор тут ни к чему, я бы как нибудь так сделал:
R>Согласен, так ещё лучше.
А чем лучше?
Как-то странно. Вроде язык высокоуровневый считается, куча умных контейнеров и указателей в библиотеках.. А как доходит до банального дерева — приходится выпиливать его руками с нуля
Здравствуйте, samius, Вы писали:
R>>Здравствуйте, sokel, Вы писали:
S>>>только вектор тут ни к чему, я бы как нибудь так сделал:
S>Как-то странно. Вроде язык высокоуровневый считается, куча умных контейнеров и указателей в библиотеках.. А как доходит до банального дерева — приходится выпиливать его руками с нуля
Здравствуйте, samius, Вы писали:
S>Если только считать что он умирает мгновенно не изменившись. Но из деструктора можно вызвать любой неконстантный метод, что делает смерть объекта не атомарной.
Объект считается мёртвым, как только начал выполняться его деструктор.
Здравствуйте, samius, Вы писали: S>Как-то странно. Вроде язык высокоуровневый считается, куча умных контейнеров и указателей в библиотеках.. А как доходит до банального дерева — приходится выпиливать его руками с нуля
Если тебе не важна супер-эффективность, то просто пишешь дерево на массиве shared_ptr детей и всё.
Если дерево надо прошить, то добавляешь регистрацию/отписывание родителя в детях.
А вот если ты начинаешь считать ресурсы и жёстко экономить, то тут уже надо подробностями заморочиться...
В каком-то смысле это не обязанность, а возможность
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, samius, Вы писали:
S>>>только вектор тут ни к чему, я бы как нибудь так сделал:
R>>Согласен, так ещё лучше.
S>А чем лучше?
S>Как-то странно. Вроде язык высокоуровневый считается, куча умных контейнеров и указателей в библиотеках.. А как доходит до банального дерева — приходится выпиливать его руками с нуля
Основная сила С++ в том, что можно и так и так.
Если делаешь прототип или утилиту, то используешь std::vector, выделяешь всё по new и не удаляешь (получается настоящий GC).
А если тебе надо будет этим парсить 100-мегабайтный XML в DOM, то тут интрузивные линки, и region аллокатор огого как пригодятся.
Здравствуйте, Centaur, Вы писали:
C>Здравствуйте, samius, Вы писали:
S>>Если только считать что он умирает мгновенно не изменившись. Но из деструктора можно вызвать любой неконстантный метод, что делает смерть объекта не атомарной.
C>Объект считается мёртвым, как только начал выполняться его деструктор.
Т.е. методы, вызванные из деструктора, выполняются уже у мертвого объекта?
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, samius, Вы писали: S>>Как-то странно. Вроде язык высокоуровневый считается, куча умных контейнеров и указателей в библиотеках.. А как доходит до банального дерева — приходится выпиливать его руками с нуля
E>Если тебе не важна супер-эффективность, то просто пишешь дерево на массиве shared_ptr детей и всё.
на данном этапе не нужна, да и вообще вряд ли.
E>Если дерево надо прошить, то добавляешь регистрацию/отписывание родителя в детях.
Что значит прошить?
E>А вот если ты начинаешь считать ресурсы и жёстко экономить, то тут уже надо подробностями заморочиться...
E>В каком-то смысле это не обязанность, а возможность
Ясно. Но тему я завел еще и что бы понять, как борются с рисками использования смартпоинтеров (вроде пересоздания shared_ptr по одному адресу T*).
Т.е. пока мне чихать на эффективность до той степени что я могу себе позволить вектора shared_ptr. Стоит ли (принято ли) в таких случаях заморачиваться упрятыванием указателей T* от греха подальше? Либо все лечится дисциплиной и передачей исключительно shared_ptr<T> по ссылке либо по значению?
Например в гвайде гугла рекомендуют воздерживаться от shared_ptr (но по другим причинам).
Здравствуйте, remark, Вы писали:
R>Здравствуйте, samius, Вы писали:
S>>Как-то странно. Вроде язык высокоуровневый считается, куча умных контейнеров и указателей в библиотеках.. А как доходит до банального дерева — приходится выпиливать его руками с нуля
R>Основная сила С++ в том, что можно и так и так. R>Если делаешь прототип или утилиту, то используешь std::vector, выделяешь всё по new и не удаляешь (получается настоящий GC). R>А если тебе надо будет этим парсить 100-мегабайтный XML в DOM, то тут интрузивные линки, и region аллокатор огого как пригодятся.
Максимум мегабайтный, но на камне в 200MHz. Сейчас основная проблема заключается в скорости создания прототипа. А я итак закис в деталях
R>
:кефир:
Здравствуйте, samius, Вы писали:
S>Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу: S>
В htmlayout/sciter дерево DOM объектов устроено таким образом:
class node: public ref_counted
{
node* parent;
collection< handle<node> > children;
};
Где handle<node> это smart ptr — делает ref_counted::add_ref/release при установке/очистке.
Делал это все руками ибо не нашел в stl в свое время подходящей идиомы которая позволяла бы это делать с той же эффективностью.
Здравствуйте, samius, Вы писали:
E>>Если дерево надо прошить, то добавляешь регистрацию/отписывание родителя в детях. S>Что значит прошить?
Добавить детям указатель на родителя.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
S>Т.е. пока мне чихать на эффективность до той степени что я могу себе позволить вектора shared_ptr. Стоит ли (принято ли) в таких случаях заморачиваться упрятыванием указателей T* от греха подальше? Либо все лечится дисциплиной и передачей исключительно shared_ptr<T> по ссылке либо по значению?
Ну вообще так строят работу, что голые указатели нигде и никогда не фигурируют.
Но в твоём случае логичнее и безопаснее юзать intrusive_ptr.
S>Например в гвайде гугла рекомендуют воздерживаться от shared_ptr (но по другим причинам).
Ну мне он тоже не нравится.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском