help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 08:32
Оценка:
Доброе время суток!
Помогите, пожалуйста, определиться:

Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу:
class Node
{
    vector< shared_ptr<Node> > _children;
    weak_ptr<Node> _parent;
    ...
};


Однако, есть ньюансы, связанные со спецификой shared_ptr, а точнее внешним хранением блока счетчиков ссылок по отношению к объекту. Нельзя создавать более одного shared_ptr по сырому указателю Node*.

Хотелось бы узнать, как принято работать с shared_ptr в таких случаях?

Варианты, которые приходят в голову следующие:
1) следить за руками, что бы ненароком не написали сырой Node* и везде принимать/передавать shared_ptr<Node>

2) инкапсулировать указатели на хранимые в shared_ptr данные.. Например, так:
class Node
{
    struct NodeData
    {
        vector<Node> _children;
        weak_ptr<NodeData> _parent;
    };
    shared_ptr<NodeData> _data;
};


Здесь узел является оберткой над 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++ по поводу вышеописанного. Как принято, как бы вы поступили в своем проекте?

И еще: верно ли я понимаю, что смартпоинтеры с управлением жизни объекта не совместимы с константностью?
Re: help: практика работы с shared_ptr
От: remark Россия http://www.1024cores.net/
Дата: 12.11.10 08:44
Оценка:
Здравствуйте, samius, Вы писали:

S>Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу:

S>
S>class Node
S>{
S>    vector< shared_ptr<Node> > _children;
S>    weak_ptr<Node> _parent;
S>    ...
S>};
S>


KISS. Когда изымаешь узел из дерева, удаляешь его. Всё.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: help: практика работы с shared_ptr
От: Kluev  
Дата: 12.11.10 08:54
Оценка: -1
Здравствуйте, samius, Вы писали:

S>Доброе время суток!

S>Помогите, пожалуйста, определиться:

S>Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу:

S>
S>class Node
S>{
S>    vector< shared_ptr<Node> > _children;
S>    weak_ptr<Node> _parent;
S>    ...
S>};
S>


Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе.
vector< shared_ptr<Node> > — фейлдизайн.
Re[2]: help: практика работы с shared_ptr
От: AndrewJD США  
Дата: 12.11.10 09:02
Оценка: +1
Здравствуйте, Kluev, Вы писали:

K>vector< shared_ptr<Node> > — фейлдизайн.

Спорное утверждение
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[2]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 09:08
Оценка: :)
Здравствуйте, remark, Вы писали:

R>KISS. Когда изымаешь узел из дерева, удаляешь его. Всё.


Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.

R>

Re[2]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 09:10
Оценка:
Здравствуйте, Kluev, Вы писали:

K>Здравствуйте, samius, Вы писали:


S>>Доброе время суток!

S>>Помогите, пожалуйста, определиться:

K>Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе.

Как уже ответил выше — дотнет ГМ. Что интересно — нет желания приучаться следить за владением объектов.

K>vector< shared_ptr<Node> > — фейлдизайн.

можно развернуть тезис?
Re[3]: help: практика работы с shared_ptr
От: remark Россия http://www.1024cores.net/
Дата: 12.11.10 09:23
Оценка: +2
Здравствуйте, samius, Вы писали:

R>>KISS. Когда изымаешь узел из дерева, удаляешь его. Всё.


S>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.


Их в принципе не получится шарить — parent и набор дочерних узлов один. Назначение этого узла в том и есть, что бы быть одним узлом одного дерева. Поэтому создал, добавил, изъял, удалил. Всё очень просто.
Пользовательские же данные — это другое дело. Пользователь может захотеть положить один объект в несколько деревьев. Но это пусть он думает, что передать в качестве T, может int, а может shared_ptr<vector<string>>.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: help: практика работы с shared_ptr
От: remark Россия http://www.1024cores.net/
Дата: 12.11.10 09:25
Оценка: +1
Здравствуйте, samius, Вы писали:

K>>Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе.

S>Как уже ответил выше — дотнет ГМ. Что интересно — нет желания приучаться следить за владением объектов.

Понимать, как работает система, и что в ней в действительности происходит, никогда не помешает. А выбор наиболее оптимального (как по простоте, так и по производительности) решения — это уже следствие.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 09:35
Оценка:
Здравствуйте, remark, Вы писали:

R>Здравствуйте, samius, Вы писали:


R>>>KISS. Когда изымаешь узел из дерева, удаляешь его. Всё.


S>>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.


R>Их в принципе не получится шарить — parent и набор дочерних узлов один. Назначение этого узла в том и есть, что бы быть одним узлом одного дерева. Поэтому создал, добавил, изъял, удалил. Всё очень просто.

Нет, шарить не в том смысле что один узел находится в нескольких деревьях, а в том, что разные объекты программы держат указатель на разные узлы дерева. И не хотелось заботиться, кто из этих объектов главный и отвечает за уничтожение дерева с его детьми.

R>Пользовательские же данные — это другое дело. Пользователь может захотеть положить один объект в несколько деревьев. Но это пусть он думает, что передать в качестве T, может int, а может shared_ptr<vector<string>>.


согласен, но я не уточнил, что речь идет не об обобщенном Node<T>, а о конкретном, например XmlNode.
R>
Re[2]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 09:47
Оценка:
Здравствуйте, Kluev, Вы писали:

K>Зачем вообще нужен этот огород? Используешь простые указатели и удаляешь узлы в деструкторе. Или еще лучше написать обертку вокруг вектора которая удаляет содержимое в своем деструкторе.

K>vector< shared_ptr<Node> > — фейлдизайн.

а что по поводу vector< scoped_ptr<Node> >?
Вобще зачем удалять что-то в деструкторе, если можно скомбинировать типы и не удалять что-то в деструкторе? В конце концов C++ а не C.
Re[3]: help: практика работы с shared_ptr
От: Erop Россия  
Дата: 12.11.10 09:49
Оценка:
Здравствуйте, samius, Вы писали:

S>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.


Если ты планируешь шарить поддеревья, то подумай о том, указатель на кого их родителей ты хочешь иметь в поддереве?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 09:52
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, samius, Вы писали:


S>>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.


E>Если ты планируешь шарить поддеревья, то подумай о том, указатель на кого их родителей ты хочешь иметь в поддереве?


речь была о том чтобы шарить ответственность за освобожденние, а не узлы между деревьями. Выше уже поправился.
Re[5]: help: практика работы с shared_ptr
От: Erop Россия  
Дата: 12.11.10 09:53
Оценка: 1 (1)
Здравствуйте, samius, Вы писали:

R>>Их в принципе не получится шарить — parent и набор дочерних узлов один. Назначение этого узла в том и есть, что бы быть одним узлом одного дерева. Поэтому создал, добавил, изъял, удалил. Всё очень просто.

S>Нет, шарить не в том смысле что один узел находится в нескольких деревьях, а в том, что разные объекты программы держат указатель на разные узлы дерева. И не хотелось заботиться, кто из этих объектов главный и отвечает за уничтожение дерева с его детьми.

А это и не надо.
Вполне достаточно, чтобы родитель, в своём деструкторе, попросил детей забыть о себе...

Я бы, либо, заюзал intrisive_ptr, либо свой. Для хранения ссылки на ноду. Как из отца, так и из клиентского кода.
И просто голый указатель, для хранения в ноде указателя на родителя.
+ Когда ноду усыновляют, в неё прописывают родителя, а когда родитель умирает, он отписывается от своих нод, и делает им Release, ну, в смысле, разрушает свои коппии intrisive_ptr...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: help: практика работы с shared_ptr
От: remark Россия http://www.1024cores.net/
Дата: 12.11.10 10:02
Оценка:
Здравствуйте, samius, Вы писали:

S>>>Помедитирую над этим. С дотнетом головного мозга тянет моделировать GC, так как нет привычки следить за владением объектами. Сейчас лихорадочно пытаюсь прикинуть, будет ли нужно шарить поддеревья, и если да, то кто будет владеть узлами.


E>>Если ты планируешь шарить поддеревья, то подумай о том, указатель на кого их родителей ты хочешь иметь в поддереве?


S>речь была о том чтобы шарить ответственность за освобожденние, а не узлы между деревьями. Выше уже поправился.


А чего тут шарить ответственность? В конец remove() добавляешь free(), кто вызвал remove(), тот вызовет и free(). О каком разделении ответственности идёт речь?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[6]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 10:06
Оценка:
Здравствуйте, remark, Вы писали:

R>Здравствуйте, samius, Вы писали:


S>>речь была о том чтобы шарить ответственность за освобожденние, а не узлы между деревьями. Выше уже поправился.


R>А чего тут шарить ответственность? В конец remove() добавляешь free(), кто вызвал remove(), тот вызовет и free(). О каком разделении ответственности идёт речь?


допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.

R>
Re[3]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 10:15
Оценка:
Здравствуйте, samius, Вы писали:

S>Здравствуйте, Kluev, Вы писали:


S>а что по поводу vector< scoped_ptr<Node> >?


прошу прощения, тут я сам лопухнул. scoped_ptr есть nocopyable, в вектор ему не пролезть.
Re[7]: help: практика работы с shared_ptr
От: Erop Россия  
Дата: 12.11.10 10:20
Оценка:
Здравствуйте, samius, Вы писали:

S>допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.


А ты хочешь, чтобы так было, или ты хочешь, чтобы так не было?
Потому, как хранить можно только подветку, вынув её из дерева, и забыв о родителях.

А ещё можно фейкового родителя иметь, кстати.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[8]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 10:24
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, samius, Вы писали:


S>>допустим, некий код построил дерево и отдал другому коду какую-то ветку (надолго, на хранение). Теперь мы обязаны заботиться что бы все дерево не было освобождено раньше чем перестанет быть нужна его ветка, отданная в другое место.


E>А ты хочешь, чтобы так было, или ты хочешь, чтобы так не было?

Хочу что бы не пришлось следить. Только тут нужно разобраться, что вреднее, не следить или хотеть.

E>Потому, как хранить можно только подветку, вынув её из дерева, и забыв о родителях.

В случае shared_ptr можно хранить и дерево и ветки как в сборе, так и отдельно вполне прозрачно. В том и прелесть.
Re: help: практика работы с shared_ptr
От: uzhas Ниоткуда  
Дата: 12.11.10 10:28
Оценка:
Здравствуйте, samius, Вы писали:

S>Доброе время суток!

S>Помогите, пожалуйста, определиться:

S>Допустим, требуется организовать дерево с указателями на родителей. shared_ptr<T> — то что доктор прописал по этому поводу:

S>
S>class Node
S>{
S>    vector< shared_ptr<Node> > _children;
S>    weak_ptr<Node> _parent;
S>    ...
S>};
S>


S>Однако, есть ньюансы, связанные со спецификой shared_ptr, а точнее внешним хранением блока счетчиков ссылок по отношению к объекту. Нельзя создавать более одного shared_ptr по сырому указателю Node*.

если вы перешли на смарт-пойнтеры, то должны уйти от сырых указателей и этого нюанса не будет в принципе

S>Меня интересует мнения практиков C++ по поводу вышеописанного. Как принято, как бы вы поступили в своем проекте?

универсальных деревьев нет, нужно смотреть специфику задачи.
если цель сделать наиболее корректное и менее подверженное ошибкам при использовании, то полностью на смарт-пойнтерах (с ручным разбитием циклов weak_ptr в помощь) (нужно четко представлять кто имеет сильную ссылку, а кому дать слабую)
если важна производительность, то на сырых указателях с ручным выделением\освобождением ресурсов (главное сформулировать четкие правила кто и когда должен освобождать ресурс)
можно однонаправленное дерево сделать (ноды не знают своего родителя), чтобы облегчить себе жизнь. для обхода можно итераторы использовать

S>И еще: верно ли я понимаю, что смартпоинтеры с управлением жизни объекта не совместимы с константностью?

не очень понятен вопрос. shared_ptr<const MyClass> вполне можно делать)
Re[2]: help: практика работы с shared_ptr
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.11.10 10:39
Оценка:
Здравствуйте, 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>?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.