Есть ли способ повысить эффективность shared_ptr путем хранения счетчика ссылок внутри контролируемого объекта? - PullRequest
3 голосов
/ 03 апреля 2010

Это становится распространенным шаблоном в моем коде, когда мне нужно управлять объектом, который должен быть некопируемым, потому что либо A. это «тяжелый», либо B. это ресурс операционной системы, такой как критический раздел :

class Resource;

class Implementation : public boost::noncopyable
{
    friend class Resource;
    HANDLE someData;
    Implementation(HANDLE input) : someData(input) {};
    void SomeMethodThatActsOnHandle() {
        //Do stuff
    };
public:
    ~Implementation() { FreeHandle(someData) };
};

class Resource
{
    boost::shared_ptr<Implementation> impl;
public:
    Resource(int argA) explicit {
        HANDLE handle = 
            SomeLegacyCApiThatMakesSomething(argA);
        if (handle == INVALID_HANDLE_VALUE)
            throw SomeTypeOfException();
        impl.reset(new Implementation(handle));
    };
    void SomeMethodThatActsOnTheResource() {
        impl->SomeMethodThatActsOnTheHandle();
    };
};

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

Тем не менее, похоже, что мы могли бы сэкономить накладные расходы на распределение счетчиков ссылок shared_ptr и такие отдельно, если бы мы могли как-то переместить эти данные в Implementation, как это делают навязчивые контейнеры boost.

Если это вызывает преждевременную оптимизацию, раздражающую некоторых людей, я на самом деле согласен, что мне не нужно это для моего текущего проекта. Но мне любопытно, если это возможно.

Ответы [ 4 ]

6 голосов
/ 03 апреля 2010

Частичным решением является использование make_shared для создания shared_ptr s.Например,

auto my_thing = std::make_shared<Thing>();

вместо

auto my_thing = std::shared_ptr<Thing>(new Thing);

Это все еще не навязчиво, поэтому больше ничего не нужно менять.Хорошие реализации make_shared объединяют выделение памяти для счетчика ссылок и самого объекта.Это экономит выделение памяти и сохраняет счетчик близко к объекту для лучшей локализации.Это не так эффективно, как boost:intrusive_ptr, но стоит подумать.

6 голосов
/ 03 апреля 2010

Используйте boost :: intrusive_ptr , который предназначен для работы с классом со встроенным счетчиком ссылок.

Непроверенный пример, основанный на примере здесь :

class Resource; 

class Implementation : public boost::noncopyable 
{ 
    friend class Resource;
    HANDLE someData;
    int refCount; // The reference count.
    Implementation(HANDLE input) : someData(input) { refCount = 0; }; 
    void SomeMethodThatActsOnHandle() { 
        //Do stuff 
    }; 
public: 
    ~Implementation() { FreeHandle(someData) }; 
};

intrusive_ptr_add_ref(Implementation* imp) { imp->refCount++; }
intrusive_ptr_release(Implementation* imp) { if(--imp->refCount) == 0) delete imp; }

class Resource 
{ 
    boost::intrusive_ptr<Implementation> impl; 
public: 
    Resource(int argA) explicit { 
        HANDLE handle =  
            SomeLegacyCApiThatMakesSomething(argA); 
        if (handle == INVALID_HANDLE_VALUE) 
            throw SomeTypeOfException(); 
        impl.reset(new Implementation(handle)); 
    }; 
    void SomeMethodThatActsOnTheResource() { 
        impl->SomeMethodThatActsOnTheHandle(); 
    }; 
}; 
1 голос
/ 03 апреля 2010

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

1 голос
/ 03 апреля 2010

Вы можете сэкономить некоторые накладные расходы, просто избавившись от двух классов и имея всего один и введя в него общий ptr - это idom, который я использую все время

 class Resource
 {
      ...
 };
 typedef boost::shared_ptr<Resource> ResourcePtr;
...