thread safe smart_pointer
От: makes Россия  
Дата: 28.08.09 21:35
Оценка:
Вот самодельный thread safe smart pointer
для polymorphic классов без встроенного счетчика ссылок.

Просьба указать на потенциальные ошибки, возможности оптимизации и т.д., в общем оценить и покритиковать.


#ifndef __SMART_POINTER__
#define __SMART_POINTER__
#pragma once


#include <map>
#include <iostream>
#include <windows.h>


// smartptr_base ======================================
class smartptr_base
{
public:
    typedef std::map<const void*,int> register_type;

    static register_type& reg()
    {
        static register_type reg;
        return reg;
    }

    static CRITICAL_SECTION *pcs()
    {
        static CRITICAL_SECTION *pcs = 0;
        if ( !pcs )
        {
            pcs = new CRITICAL_SECTION;
            ::InitializeCriticalSection(pcs);
        }
        return pcs;
    }

    static void lock()
    {
        ::EnterCriticalSection(pcs());
    }

    static void unlock()
    {
        ::LeaveCriticalSection(pcs());
    }

    static int increase_refcount(const void *pt)
    {
        lock();

        int refcnt = ++reg()[pt];

        unlock();
        return refcnt;
    }

    static int decrease_refcount(const void *pt)
    {
        lock();
        int refcnt = --reg()[pt];

        if ( !refcnt )
            reg().erase(pt);

        unlock();
        return refcnt;
    }
};

// smartptr =================================================
template<class T>
class smartptr : smartptr_base
{
public:
    typedef T element_type;

    /*explicit*/ smartptr(const T *pt = 0)
        : m_pt( 0 ) //init member to zero!
    {
        reset( pt );
    }

    template<class U>
    /*explicit*/ smartptr(const U *pu)
        : m_pt( 0 ) //init member to zero!
    {
        reset( dynamic_cast<const T*>(pu) );
    }

    smartptr(const smartptr& sp)
        : m_pt( 0 ) //init member to zero!
    {
        reset( sp.m_pt );
    }

    template<class U>
    smartptr(const smartptr<U>& sp)
        : m_pt( 0 ) //init member to zero!
    {
        reset( dynamic_cast<const T*>(sp.get()) );
    }

    ~smartptr()
    {
        reset();
    }

    smartptr& reset(const T *pt = 0)
    {
        if ( m_pt == pt )
            return *this;

        if ( m_pt && !decrease_refcount(dynamic_cast<const void*>(m_pt)) )
            delete m_pt;

        if ( pt )
            increase_refcount(dynamic_cast<const void*>(pt));

        m_pt = pt;
        return *this;
    }

    // standard operations ----------------------------------------------
          T *get               ()       { return const_cast<T*>(m_pt); }
    const T *get               () const { return m_pt; }
             operator       T* ()       { return  get(); }
             operator const T* () const { return  get(); }
          T& operator *        ()       { return *get(); }
    const T& operator *        () const { return *get(); }
          T *operator ->       ()       { return  get(); }
    const T *operator ->       () const { return  get(); }

             operator bool     () const { return m_pt ? true  : false; }
    bool     operator !        () const { return !(bool)(*this); }

    smartptr& operator = (const smartptr& sp)
    {
        return reset( sp.get() );
    }

    template<class U>
    smartptr& operator = (const smartptr<U>& sp)
    {
        return reset( dynamic_cast<const T*>(sp.get()) );
    }

private:
    const T *m_pt;
};

#endif//__SMART_POINTER__
Re: между прочим boost::shared_ptr thread-safe'ный
От: Аноним  
Дата: 28.08.09 21:57
Оценка:
Re: thread safe smart_pointer
От: Хвост  
Дата: 28.08.09 22:11
Оценка:
Здравствуйте, makes, Вы писали:

M>Вот самодельный thread safe smart pointer


увидел static и дальше было лениво смотреть.
People write code, programming languages don't.
Re[2]: thread safe smart_pointer
От: makes Россия  
Дата: 28.08.09 22:43
Оценка:
Здравствуйте, Хвост, Вы писали:

Х>увидел static и дальше было лениво смотреть.

а что не так со static? почему он лень навивает? он только в базовом классе — где подсчет ссылок идет, а дальше более-менее стандартный template-ный смартпоинтер.
Re: thread safe smart_pointer
От: Кодт Россия  
Дата: 28.08.09 22:53
Оценка: 30 (1) +1
Здравствуйте, makes, Вы писали:

M>
M>// smartptr_base ======================================

// это не "базовый класс указателя", а аллокатор счётчиков.
// называй вещи своими именами.

M>class smartptr_base
M>{
M>public:
M>    typedef std::map<const void*,int> register_type;

M>    static register_type& reg()
M>    {

// гонки при инициализации статической переменной

M>        static register_type reg;
M>        return reg;
M>    }

M>    static CRITICAL_SECTION *pcs()
M>    {

// сомнительный code style - локальное имя совпадает с глобальным (pcs)

// гонки при инициализации, возможность получить несколько экземпляров

M>        static CRITICAL_SECTION *pcs = 0;
M>        if ( !pcs )
M>        {

// утечка памяти и системных ресурсов (нет парного ::DestroyCriticalSection и delete pcs)

M>            pcs = new CRITICAL_SECTION;
M>            ::InitializeCriticalSection(pcs);
M>        }
M>        return pcs;
M>    }

M>    static void lock()
M>    {
M>        ::EnterCriticalSection(pcs());
M>    }

M>    static void unlock()
M>    {
M>        ::LeaveCriticalSection(pcs());
M>    }

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

M>    static int increase_refcount(const void *pt)
M>    {
M>        lock();

M>        int refcnt = ++reg()[pt];

M>        unlock();
M>        return refcnt;
M>    }

M>    static int decrease_refcount(const void *pt)
M>    {
M>        lock();
M>        int refcnt = --reg()[pt];

M>        if ( !refcnt )
M>            reg().erase(pt);

M>        unlock();
M>        return refcnt;
M>    }
M>};

M>// smartptr =================================================
M>template<class T>
M>class smartptr : smartptr_base
M>{
M>public:
M>    typedef T element_type;

// /*explicit*/ - можно случайно инициализировать указателем на объект, который нельзя удалять.
// огромная ошибкоопасность.

M>    /*explicit*/ smartptr(const T *pt = 0)
M>        : m_pt( 0 ) //init member to zero!
M>    {
M>        reset( pt );
M>    }

M>    template<class U>
M>    /*explicit*/ smartptr(const U *pu)
M>        : m_pt( 0 ) //init member to zero!
M>    {

// dynamic_cast - невозможно работать с типами, не являющимися полиморфными классами.

M>        reset( dynamic_cast<const T*>(pu) );
M>    }

M>    smartptr(const smartptr& sp)
M>        : m_pt( 0 ) //init member to zero!
M>    {
M>        reset( sp.m_pt );
M>    }

M>    template<class U>
M>    smartptr(const smartptr<U>& sp)
M>        : m_pt( 0 ) //init member to zero!
M>    {
M>        reset( dynamic_cast<const T*>(sp.get()) );
M>    }

M>    ~smartptr()
M>    {
M>        reset();
M>    }

M>    smartptr& reset(const T *pt = 0)
M>    {

// как насчёт множественного наследования?

M>        if ( m_pt == pt )
M>            return *this;

M>        if ( m_pt && !decrease_refcount(dynamic_cast<const void*>(m_pt)) )
M>            delete m_pt;

M>        if ( pt )
M>            increase_refcount(dynamic_cast<const void*>(pt));

M>        m_pt = pt;
M>        return *this;
M>    }

M>    // standard operations ----------------------------------------------

// глубокая константность - не типичная для указателя семантика

M>          T *get               ()       { return const_cast<T*>(m_pt); }
M>    const T *get               () const { return m_pt; }
M>             operator       T* ()       { return  get(); }
M>             operator const T* () const { return  get(); }
M>          T& operator *        ()       { return *get(); }
M>    const T& operator *        () const { return *get(); }
M>          T *operator ->       ()       { return  get(); }
M>    const T *operator ->       () const { return  get(); }

// при наличии operator T* определение operator bool избыточно
// кстати, здравствуй, хинди-код; можно было короче: return m_pt!=0

M>             operator bool     () const { return m_pt ? true  : false; }

// при наличии operator bool определение operator! избыточно.
// оно потребовалось из-за неоднозначности, создавшейся из-за объявления operator bool вдобавок к operator T*

M>    bool     operator !        () const { return !(bool)(*this); }

M>    smartptr& operator = (const smartptr& sp)
M>    {
M>        return reset( sp.get() );
M>    }

M>    template<class U>
M>    smartptr& operator = (const smartptr<U>& sp)
M>    {
M>        return reset( dynamic_cast<const T*>(sp.get()) );
M>    }

M>private:
M>    const T *m_pt;
M>};
M>
Перекуём баги на фичи!
Re[3]: thread safe smart_pointer
От: Кодт Россия  
Дата: 28.08.09 22:55
Оценка:
Здравствуйте, makes, Вы писали:

Х>>увидел static и дальше было лениво смотреть.

M>а что не так со static? почему он лень навивает? он только в базовом классе — где подсчет ссылок идет, а дальше более-менее стандартный template-ный смартпоинтер.

Потому что это thread-unsafe синглетон Мейерса. Да ещё и с ошибками.
Перекуём баги на фичи!
Re[2]: между прочим boost::shared_ptr thread-safe'ный
От: makes Россия  
Дата: 28.08.09 23:12
Оценка:
я не до конца разобрался с бустовскими смартпоинтерами => изобретаю велосипед
Re[2]: thread safe smart_pointer
От: makes Россия  
Дата: 29.08.09 00:17
Оценка:
Спасибо, Кодт!

Есть вопросы (ответы):

1.
// порочная идея - хранить счётчики в общей помойке.
// во-первых, каждая операция - O(logN)
// во-вторых, бутылочное горлышко (невозможность двум независимым потокам параллельно работать)
M>    static int increase_refcount(const void *pt)
M>    {
M>        lock();
M>        int refcnt = ++reg()[pt];
M>        unlock();
M>        return refcnt;
M>    }

а как иначе? ведь переносить функциональность "аллокатора счётчиков" в smart_ptr<T> нельзя, так как для T1 и T2 будут разные счетчики даже если T1 и T2 "полиморфно связаны" и T1 *p1 и T2 *p2 указывают на один и тот-же объект,
кстати поэтому я использую dynamic_cast<void*>.

2.
// /*explicit*/ - можно случайно инициализировать указателем на объект, который нельзя удалять.
// огромная ошибкоопасность.
M>    /*explicit*/ smartptr(const T *pt = 0)
M>        : m_pt( 0 ) //init member to zero!
M>    {
M>        reset( pt );
M>    }


а в чем разница:
smartptr<X> ptr = SomeThing;

или
smartptr<X> ptr( SomeThing );

в обоих случаях это возможно.
(правда explicit закомментировал для удобства использования).

3.
// dynamic_cast - невозможно работать с типами, не являющимися полиморфными классами.


использую dynamic_cast<void*> для получения адреса объекта, потом адрес использую как ключ в std::map;
можно как-то иначе?

4.
// как насчёт множественного наследования?
M>        if ( m_pt == pt )
M>            return *this;
M>        if ( m_pt && !decrease_refcount(dynamic_cast<const void*>(m_pt)) )
M>            delete m_pt;
M>        if ( pt )
M>            increase_refcount(dynamic_cast<const void*>(pt));
M>        m_pt = pt;
M>        return *this;


все в порядке, по моему, все указатели (все типы) приводятся к void* динамик_кастом
Re[3]: между прочим boost::shared_ptr thread-safe'ный
От: ononim  
Дата: 29.08.09 00:34
Оценка:
M>я не до конца разобрался с бустовскими смартпоинтерами => изобретаю велосипед
см подпись
Как много веселых ребят, и все делают велосипед...
Re: thread safe smart_pointer
От: remark Россия http://www.1024cores.net/
Дата: 30.08.09 14:07
Оценка:
Здравствуйте, makes, Вы писали:

M>Вот самодельный thread safe smart pointer

M>для polymorphic классов без встроенного счетчика ссылок.
M>Просьба указать на потенциальные ошибки, возможности оптимизации и т.д., в общем оценить и покритиковать.



M> smartptr& reset(const T *pt = 0)

M> {
M> if ( m_pt == pt )
M> return *this;
M> if ( m_pt && !decrease_refcount(dynamic_cast<const void*>(m_pt)) )
M> delete m_pt;
M> if ( pt )
M> increase_refcount(dynamic_cast<const void*>(pt));
M> m_pt = pt;
M> return *this;
M> }


В такой ситуации необходимо вначале инкрементировать счётчик для нового объекта, а потом декрементировать для старого. В противном случае ты можешь налететь на ситуацию, что новый объект уже разрушен к моменту инкремента. Представь, есть объект А, который содержит умный указатель на объект Б. Объект А держится только одним умным указателем. Теперь мы для этого умного указателя (который держит А) делаем reset() на Б. После decrease_refcount() для А, А и Б удаляются. И потом мы пытаемся сделать increase_refcount() для уже удаленного Б.




M> static CRITICAL_SECTION *pcs()

M> {
M> static CRITICAL_SECTION *pcs = 0;
M> if ( !pcs )
M> {
M> pcs = new CRITICAL_SECTION;
M> ::InitializeCriticalSection(pcs);
M> }
M> return pcs;
M> }


Эта инициализация должна быть потокобезопасной. Если интересует только студия+винда, то что-то типа такого:


static CRITICAL_SECTION *pcs()
{
  static CRITICAL_SECTION* volatile pcs = 0;
  if (pcs == 0)
  {
    CRITICAL_SECTION* tmp = new CRITICAL_SECTION;
    ::InitializeCriticalSection(tmp);
    if (_InterlockedCompareExchange((long*)&pcs, (long)tmp, 0))
    {
      DeleteCriticalSection(tmp);
      delete tmp;
    }
  }
  return pcs;
}


C pthreads или boost это проще, т.к. их мьютексы поддерживают статическую инициализацию (что очень логично). Т.е. можно писать вот так:
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

К сожалению виндовая критическая секция этого не поддерживает.



M> static int increase_refcount(const void *pt)

M> {
M> lock();
M> int refcnt = ++reg()[pt];
M> unlock();
M> return refcnt;
M> }

При доступе к std::map<> может возникнуть исключение std::bad_alloc, если это произойдёт, то вся система зависнет намертво. Поэтому корректнее будет:
static int increase_refcount(const void *pt)
{
  scoped_lock lock (pcs());
  return ++reg()[pt];
}

smartptr::reset() тоже должна учитывать, что increase_refcount() может кинуть исключение. Но вроде выполнение increase_refcount() первым действием решает проблему.



Что касается производительности и оптимизации, то прямо скажем, что текущий дизайн достаточно плох. Тут и теоретическая сложность операций O(logN), т.е. чем больше объектов, тем тормознее система. Тут и плохая реализация — операции требуют аллокации вспомогательных объектов + отвратительная локальность обращений. Тут и полная централизация, которая глушит любую concurrency на корню и вносит колоссальные издержки при исполнении на физически параллельном железе.

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

Как некоторые подпорки для текущей реализации можно предложить партицировать все данные, связанные с подсчётом ссылок, по хэшу от указателя. Что-то типа такого:
struct part_t
{
  mutex_t mtx;
  map<void*, unsigned> rc;
};

struct padded_part_t : part_t
{
  char pad [cache_line_size - sizeof(part_t)];
};

size_t const part_count = 1024;
padded_part_t g_parts [1024];

void increase_rc(void* obj)
{
  size_t part_idx = hash(obj) % part_count;
  part_t& part = g_parts[part_idx];
  scoped_lock_t lock (part.mtx);
  part.rc[obj] += 1;
}


Ну и плюс использовать свой slab аллокатор для map с кэшем в каждом потоке.
Это несколько снизит теоретическую сложность до O(log(N/part_count)), несколько улучшит реализацию, и даст некоторую concurrency.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: thread safe smart_pointer
От: remark Россия http://www.1024cores.net/
Дата: 30.08.09 14:19
Оценка:
Здравствуйте, makes, Вы писали:

M>1.

M>
M>// порочная идея - хранить счётчики в общей помойке.
M>// во-первых, каждая операция - O(logN)
M>// во-вторых, бутылочное горлышко (невозможность двум независимым потокам параллельно работать)
M>>    static int increase_refcount(const void *pt)
M>>    {
M>>        lock();
M>>        int refcnt = ++reg()[pt];
M>>        unlock();
M>>        return refcnt;
M>>    }
M>

M>а как иначе? ведь переносить функциональность "аллокатора счётчиков" в smart_ptr<T> нельзя, так как для T1 и T2 будут разные счетчики даже если T1 и T2 "полиморфно связаны" и T1 *p1 и T2 *p2 указывают на один и тот-же объект,
M>кстати поэтому я использую dynamic_cast<void*>.

Приводи любой тип, переданный пользователем, к своему rc_base_t, и дальше с ним работай.
В любом MDT (most derived type) объекте должен быть только один rc_base_t, и он должен быть виден и доступен из всех базовых подобъектов, для которых создаются умные указатели. Обеспечение этого — обязанность пользователя.


M>2.

M>
M>// /*explicit*/ - можно случайно инициализировать указателем на объект, который нельзя удалять.
M>// огромная ошибкоопасность.
M>>    /*explicit*/ smartptr(const T *pt = 0)
M>>        : m_pt( 0 ) //init member to zero!
M>>    {
M>>        reset( pt );
M>>    }
M>


M>а в чем разница:

M>
M>smartptr<X> ptr = SomeThing;
M>

M>или
M>
M>smartptr<X> ptr( SomeThing );
M>

M>в обоих случаях это возможно.
M>(правда explicit закомментировал для удобства использования).

Я думаю, что Кодт имел в виду что-то типа такого:
void foo(X& x, ...)
{
  ...
  bar(&x);
  ...
}


Без explicit конструктора тут можно получить неприятные сюрпризы. Причём даже вначале этот код может работать, а потом кто-то по наивности изменяет сигнатуру функции bar(), т.к. что она начинает принимать умный указатель, к его радости весь проект компилируется успешно, однако потом начинаются неприятные сюрпризы.
Если конструктор explicit, то код не скомпилируется. Да и вообще такая запись более отражает суть происходящего:
void foo(X* x, ...)
{
  ...
  bar(smart_ptr<X>(x));
  ...
}





M>3.

M>
M>// dynamic_cast - невозможно работать с типами, не являющимися полиморфными классами.
M>


M>использую dynamic_cast<void*> для получения адреса объекта, потом адрес использую как ключ в std::map;

M>можно как-то иначе?


Так где Кодт это написал был не dynamic_cast<void*>, а dynamic_cast<T const*>.



M>4.

M>
M>// как насчёт множественного наследования?
M>>        if ( m_pt == pt )
M>>            return *this;
M>>        if ( m_pt && !decrease_refcount(dynamic_cast<const void*>(m_pt)) )
M>>            delete m_pt;
M>>        if ( pt )
M>>            increase_refcount(dynamic_cast<const void*>(pt));
M>>        m_pt = pt;
M>>        return *this;
M>


M>все в порядке, по моему, все указатели (все типы) приводятся к void* динамик_кастом


Тут проблема даже не во множественном наследовании, а в том, что increase_refcount() должен стоять перед decrease_refcount(). Тогда == проверку можно убрать совсем.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: между прочим boost::shared_ptr thread-safe'ный
От: remark Россия http://www.1024cores.net/
Дата: 30.08.09 14:30
Оценка:
Здравствуйте, ononim, Вы писали:

M>>я не до конца разобрался с бустовскими смартпоинтерами => изобретаю велосипед

O>см подпись

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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: между прочим boost::shared_ptr thread-safe'ный
От: Alexander G Украина  
Дата: 31.08.09 08:31
Оценка: +1
Между прочим, в узком смысле. Одновременный reset и get из разных нитей недопустим.
Было бы интересно на полностью потокобезопасные умные указатели на атоомарных операциях.
Русский военный корабль идёт ко дну!
Re[5]: между прочим boost::shared_ptr thread-safe'ный
От: bkat  
Дата: 31.08.09 08:47
Оценка:
Здравствуйте, remark, Вы писали:

R>По-моему, хотя бы раз такой велосипед стоит сделать самому. Для само-развития. По-крайней мере я не жалею, что делал...


В учебных целях и в самом деле полезно, но на реальном проекте боже упаси...
Re[3]: thread safe smart_pointer
От: Кодт Россия  
Дата: 31.08.09 11:14
Оценка:
Здравствуйте, makes, Вы писали:

<>

Твоя основная идея — в том, чтобы
— сделать подсчёт ссылок неинтрузивным — т.е. вынести счётчик за пределы объекта
— при этом сделать его прозрачным

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

У прозрачности оказывается слишком высокая цена: бутылочное горлышко, проблемы с не полиморфными классами, ошибкоопасность.
Вот и подумай: нужна ли она тебе?

Если нужна для узкого семейства классов — тогда подумай, нужна ли тебе тогда неинтрузивность?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[2]: thread safe smart_pointer
От: makes Россия  
Дата: 04.09.09 19:37
Оценка:
Здравствуйте, Кодт, Вы писали:

К>// гонки при инициализации статической переменной

M>> static register_type reg;
M>> return reg;
M>> }
/* ... */
К>// гонки при инициализации, возможность получить несколько экземпляров
M>> static CRITICAL_SECTION *pcs = 0;
M>> if ( !pcs )
M>> {

а как избавиться от гонок? инициализация библиотеки в main/WinMain?


К>// порочная идея — хранить счётчики в общей помойке.

К>// во-первых, каждая операция — O(logN)
К>// во-вторых, бутылочное горлышко (невозможность двум независимым потокам параллельно работать)

M>> static int increase_refcount(const void *pt)

M>> {
M>> lock();

M>> int refcnt = ++reg()[pt];


M>> unlock();

M>> return refcnt;
M>> }

M>> static int decrease_refcount(const void *pt)

M>> {
M>> lock();
M>> int refcnt = --reg()[pt];

M>> if ( !refcnt )

M>> reg().erase(pt);

M>> unlock();

M>> return refcnt;
M>> }
M>>};

"... O(logN) ..." — это, я так понимаю, неизбежно при "неинтрузивности"?

"... невозможность двум независимым потокам параллельно работать ..." — а в чем проблема, один поток меняет reg, другой ждет?

я правильно понял: лучше использовать интрузивные счетчики?



К>// глубокая константность — не типичная для указателя семантика

M>> T *get () { return const_cast<T*>(m_pt); }
M>> const T *get () const { return m_pt; }
M>> operator T* () { return get(); }
M>> operator const T* () const { return get(); }
M>> T& operator * () { return *get(); }
M>> const T& operator * () const { return *get(); }
M>> T *operator -> () { return get(); }
M>> const T *operator -> () const { return get(); }

А вот ты еще писал про агрегаты в похожей теме:
http://www.rsdn.ru/forum/cpp/3519449.1.aspx
Автор: Кодт
Дата: 28.08.09

А можно ссылку научно-популярную, про агрегаты почитать?


К>// при наличии operator T* определение operator bool избыточно

К>// кстати, здравствуй, хинди-код; можно было короче: return m_pt!=0

M>> operator bool () const { return m_pt ? true : false; }


К>// при наличии operator bool определение operator! избыточно.

К>// оно потребовалось из-за неоднозначности, создавшейся из-за объявления operator bool вдобавок к operator T*

то есть при наличии operator T* избыточны оба оператора: bool и !?

"... хинди-код; можно было короче: return m_pt!=0 ..." -- "иудаизм" — это от ОЧЕНЬ большого ума.
Re[3]: thread safe smart_pointer
От: makes Россия  
Дата: 05.09.09 07:28
Оценка:
я имел ввиду "индуизм" конечно ( )
Re[3]: thread safe smart_pointer
От: Кодт Россия  
Дата: 07.09.09 10:19
Оценка:
Здравствуйте, makes, Вы писали:

M>а как избавиться от гонок? инициализация библиотеки в main/WinMain?


Вообще, корректные синглетоны — это очень увлекательное дело.
Можно инициализировать явно — но для такой низкоуровневой библиотеки это выглядит издевательски.
Если хочешь разобраться, читай Александреску и Саттера, они про синглетоны много написали.

Самое примитивное решение — это завести глобальные переменные CCriticalSection и map<void*,int>.
Их конструкторы выполнятся до main в однопоточное время, и не создадут проблем.

Там будет другая беда: нужно как-то договориться с линкером. То есть, либо поместить их в отдельный .cpp, либо специальными трюками объявить их selectany.
У MSVC это делается __declspec(selectany), GCC — не знаю, есть ли такой атрибут.
Без расширений языка — используется тот факт, что inline-функции и статические члены шаблонов являются selectany. Поэтому и говорю: "трюки".

К>>// порочная идея — хранить счётчики в общей помойке.

К>>// во-первых, каждая операция — O(logN)
К>>// во-вторых, бутылочное горлышко (невозможность двум независимым потокам параллельно работать)

M>"... O(logN) ..." — это, я так понимаю, неизбежно при "неинтрузивности"?

Это следствие того, что у тебя монолитный реестр подконтрольных объектов, сделанный на деревянном контейнере.
Хэш-таблица, кстати, тоже не выручит. Хотя надо смотреть, сколько объектов живёт в системе одновременно.

M>"... невозможность двум независимым потокам параллельно работать ..." — а в чем проблема, один поток меняет reg, другой ждет?

Проблема в том, что при каждом обращении к умным указателям (и, следовательно, к реестру) все потоки ломятся в единственную дверь строго по очереди.
Ладно бы, если два потока пытались работать с указателями на один объект (и, следовательно, с одним счётчиком). Тут сериализация необходима. А когда с разными объектами — зачем?

M>я правильно понял: лучше использовать интрузивные счетчики?


Достаточно, чтобы каждый указатель имел прямой доступ к своему счётчику, а не лазил туда через реестр.
Тогда тормоза будут лишь в точках создания-убивания объектов.
То есть,
template<class T>
class smart_ptr
{
    T*   m_ptr;
    int* m_counter;
public:
    smart_ptr()
        : m_ptr(0)
        , m_counter(0)
        {}
    smart_ptr(T* p)
        : m_ptr(p),
        , m_counter(p ? take_the_counter(dynamic_cast<void*>(p)) : 0)) // регистрирует новый или получает старый счётчик
        {
            it(m_counter)
                atomic_inc(&m_counter);
        }
    smart_ptr(smart_ptr const& other)
        : m_ptr(other.m_ptr)
        , m_counter(other.m_counter)
        {
            if(m_counter)
                atomic_inc(&m_counter);
        }
    ~smart_ptr()
        {
            if(m_counter && atomic_dec(&m_counter)==0)
            {
                remove_the_counter(dynamic_cast<void*>(p), m_counter);
                delete m_ptr;
            }
        }
    .....
};

(Вроде бы не ошибся нигде...)


К>>// глубокая константность — не типичная для указателя семантика


M>А вот ты еще писал про агрегаты в похожей теме:

M>http://www.rsdn.ru/forum/cpp/3519449.1.aspx
Автор: Кодт
Дата: 28.08.09

M>А можно ссылку научно-популярную, про агрегаты почитать?

Я называю там агрегатами то, что входит в состав объекта:
— базовые подобъекты
— члены
— сторонние объекты, которыми данный объект монопольно владеет и распоряжается

Очевидно, что указатель сам по себе не распоряжается указуемым объектом, а только служит для управления его видимостью и временем жизни.
А вот фасад PIMPL, сделанный из указателя — распоряжается своим единственным агрегатом.


M>то есть при наличии operator T* избыточны оба оператора: bool и !?


Именно.
Дистиллированный пример
struct foo
{
    // просто некое определение приведения к указателю. без смысла.
    operator void const* () const { return this; }
};

int main()
{
    foo f;
    // по-всякому приводим объект к bool
    bool b1 = f;
    bool b2 = !f;
    bool b3 = f ? true : false;
    bool b4 = f && f;
}


Иногда, чтобы избежать нежелательных приведений к bool (b1), оставив лишь логику (b2..b4), вводят специально приведение к unspecified bool type.
Посмотри в бусте, как это делается, или поищи на RSDN.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re: thread safe smart_pointer
От: Кодт Россия  
Дата: 07.09.09 10:19
Оценка:
Здравствуйте, makes, Вы писали:


Увидел ещё одну беду и огорчение. А именно: smartptr в reset делает delete m_pt.
Теперь допустим, что этот m_pt — база без виртуального деструктора. UB.

Причём компилироваться всё будет: для dynamic_cast<void*> необходимо и достаточно наличие хоть какой-нибудь виртуальной функции, а не обязательно деструктора.

Вот, в том числе против этой проблемы борются в boost::shared_ptr, сохраняя функцию-разрушитель исходного объекта.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.