Увеличение и уменьшение значения boost::shared_ptr вручную?

StackOverflow https://stackoverflow.com/questions/1482806

  •  18-09-2019
  •  | 
  •  

Вопрос

Есть ли способ вручную увеличивать и уменьшать счетчик общего_птра в С++?

Проблема, которую я пытаюсь решить, заключается в следующем.Я пишу библиотеку на C++, но интерфейс должен быть на чистом C.Внутри я хотел бы использоватьshared_ptr, чтобы упростить управление памятью, сохраняя при этом возможность передавать необработанный указатель через интерфейс C.

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

Это было полезно?

Решение

В вашем предложении

Затем клиент будет нести ответственность за уменьшение значения счетчика.

означает, что клиент, о котором идет речь, несет ответственность за управление памятью и что вы доверяете ему.Я до сих пор не понимаю, почему.

Фактически изменить счетчик shared_ptr невозможно...(хм, я объясню в конце, как это сделать ...) но есть и другие решения.

Решение 1:полная собственность клиента

Передайте указатель клиенту (shared_ptr:: выпуск) и ожидайте, что он вернет вам право собственности при обратном вызове (или просто удалит объект, если он на самом деле не является общим).

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

Решение 2:с обратным вызовом

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

struct Object;

class Pool // may be a singleton, may be synchronized for multi-thread usage
{
public:
  int accept(boost::shared_ptr<Object>); // adds ptr to the map, returns NEW id
  void release(int id) { m_objects.erase(id); }

private:
  std::map< int, boost::shared_ptr<Object> > m_objects;
}; // class Pool

Таким образом, ваш клиент, "уменьшающий" счетчик, на самом деле является вашим клиентом, вызывающим метод обратного вызова с идентификатором, который вы использовали, и вы удаляете один shared_ptr :)

Усиление взлома::shared_ptr

Как я уже сказал, возможно (поскольку мы работаем на C ++) фактически взломать shared_ptr.Есть даже несколько способов сделать это.

Тот Самый Лучшие способ (и самый простой) - просто скопировать файл под другим именем (my_shared_ptr ?), а затем:

  • измените параметры включения
  • включите реальный shared_ptr в начале
  • переименуйте любой экземпляр shared_ptr своим собственным именем (и измените private на public для доступа к атрибутам)
  • удалите все, что уже определено в реальном файле, чтобы избежать столкновений

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

Я намеренно опустил трюк с 'reinterpret_cast' и смещением указателя.Существует так много способов получить нелегальный доступ к чему-либо на C / C ++!

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

Другие советы

Возможно, вы используете boost::shared_ptr через границы DLL, что не будет работать должным образом.В этом случае boost::inrusive_ptr может помочь вам.Это частый случай злоупотребления shared_ptr люди пытаются обойти это грязными хаками...Возможно, я ошибаюсь в вашем случае, но не должно быть веской причины делать то, что вы пытаетесь сделать ;-)

ДОБАВЛЕНО 07.2010:Кажется, проблемы возникают больше из-за загрузки/выгрузки DLL, чем из-за самого Shared_ptr.Даже обоснование повышения мало что говорит о случаях, когда boost::intrusive_ptr следует отдать предпочтение перед shared_ptr.Я переключился на разработку .NET и не следил за подробностями TR1 по этой теме, поэтому будьте осторожны, этот ответ сейчас может быть недействителен...

1.Ручка?

Если вы хотите максимальной безопасности, предоставьте пользователю дескриптор, а не указатель.Таким образом, он ни за что не попытается free это и наполовину удается.

Ниже я предположу, что для простоты вы предоставите пользователю указатель на объект.

2.приобретать и не приобретать ?

Вы должны создать класс manager, как описано Матье М.в его ответ, чтобы запомнить то, что было приобретено / не приобретено пользователем.

Поскольку inferface - это C, вы не можете ожидать, что он будет использовать delete или что там еще.Итак, заголовок, подобный:

#ifndef MY_STRUCT_H
#define MY_STRUCT_H

#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus

typedef struct MyStructDef{} MyStruct ; // dummy declaration, to help
                                        // the compiler not mix types

MyStruct * MyStruct_new() ;
size_t     MyStruct_getSomeValue(MyStruct * p) ;
void       MyStruct_delete(MyStruct * p) ;

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // MY_STRUCT_H

Позволит пользователю использовать ваш класс.Я использовал объявление фиктивной структуры, потому что я хочу помочь пользователю C, не навязывая ему использование универсального void * указатель.Но используя void * это все равно хорошо.

Исходным кодом C ++, реализующим эту функцию, будет:

#include "MyClass.hpp"
#include "MyStruct.h"

MyManager g_oManager ; // object managing the shared instances
                       // of your class

extern "C"
{

MyStruct * MyStruct_new()
{
   MyClass * pMyClass = g_oManager.createMyClass() ;
   MyStruct * pMyStruct = reinterpret_cast<MyStruct *>(pMyClass) ;
   return pMyStruct ;
}

size_t MyStruct_getSomeValue(MyStruct * p)
{
   MyClass * pMyClass = reinterpret_cast<MyClass *>(p) ;

   if(g_oManager.isMyClassExisting(pMyClass))
   {
      return pMyClass->getSomeValue() ;
   }
   else
   {
      // Oops... the user made a mistake
      // Handle it the way you want...
   }

   return 0 ;
}

void MyStruct_delete(MyStruct * p)
{
   MyClass * pMyClass = reinterpret_cast<MyClass *>(p) ;
   g_oManager.destroyMyClass(pMyClass) ;
}

}

Обратите внимание, что указатель на MyStruct является явно недопустимым.Вы не должны использовать его по какой-либо причине без переинтерпретации его в исходный тип MyClass (см. Jaif's ответ для получения дополнительной информации об этом.Пользователь C будет использовать его только с соответствующими функциями MyStruct_*.

Обратите также внимание, что этот код проверяет, что класс действительно существует.Это может быть излишеством, но это возможное использование менеджера (см. Ниже).

3.О менеджере

Менеджер будет хранить, как предложил Матье М., карту, содержащую общий указатель в качестве значения (и сам указатель, или дескриптор, в качестве ключа).Или мультимап, если пользователь может каким-то образом получить один и тот же объект несколько раз.

Хорошей особенностью использования менеджера будет то, что ваш код на C ++ сможет отслеживать, какие объекты не были "не получены" пользователем правильно (добавляя информацию в методы получения / не получения, такие как __FILE__ и __LINE__ могло бы помочь сузить поиск ошибок).

Таким образом, менеджер сможет:

  1. НЕ освобождать несуществующий объект (кстати, как пользователю C удалось его приобрести?)
  2. ЗНАЙТЕ в конце выполнения, какие объекты не остались невостребованными
  3. В случае, если объекты не приобретены, уничтожайте их в любом случае (что хорошо с точки зрения RAII) Это несколько порочно, но вы могли бы предложить это
  4. Как показано в приведенном выше коде, это может даже помочь обнаружить, что указатель не указывает на допустимый класс

Здесь вам следует выполнить разделение задач:если клиент передает необработанный указатель, клиент будет отвечать за управление памятью (т. е.потом почисти).Если вы создаете указатели, вы будете отвечать за управление памятью.Это также поможет вам решить проблемы с границами DLL, упомянутые в другом ответе.

Я столкнулся со случаем использования, когда мне действительно нужно было что-то подобное, связанное с IOCompletionPorts и проблемами параллелизма.Хакерский, но соответствующий стандартам метод состоит в том, чтобы адвокат это как описано Хербом Саттером здесь.

Следующий фрагмент кода предназначен для std::shared_ptr, реализованного в VC11:

Файл реализации:

namespace {
    struct HackClass {
        std::_Ref_count_base *_extracted;
    };
}

template<>
template<>
void std::_Ptr_base<[YourType]>::_Reset<HackClass>(std::auto_ptr<HackClass> &&h) {
     h->_extracted = _Rep; // Reference counter pointer
}

std::_Ref_count_base *get_ref_counter(const std::shared_ptr<[YourType]> &p) {
     HackClass hck;
     std::auto_ptr<HackClass> aHck(&hck);

     const_cast<std::shared_ptr<[YourType]>&>(p)._Reset(std::move(aHck));

     auto ret = hck._extracted; // The ref counter for the shared pointer
                                // passed in to the function

     aHck.release(); // We don't want the auto_ptr to call delete because
                     // the pointer that it is owning was initialized on the stack

     return ret;
}

void increment_shared_count(std::shared_ptr<[YourType]> &sp) {
     get_ref_counter(sp)->_Incref();
}

void decrement_shared_count(std::shared_ptr<[YourType]> &sp) {
     get_ref_counter(sp)->_Decref();
}

Замените [YourType] типом объекта, для которого необходимо изменить счетчик.Важно отметить, что это довольно хакерски и использует имена объектов, специфичные для платформы.Объем работы, которую вам придется проделать, чтобы получить эту функциональность, вероятно, свидетельствует о том, насколько плоха эта идея.Кроме того, я играю в игры с auto_ptr, потому что функция, которую я перехватываю из Shared_ptr, принимает auto_ptr.

Другой вариант — просто динамически выделить копию Shared_ptr, чтобы увеличить счетчик ссылок, и освободить ее, чтобы уменьшить.Это гарантирует, что мой общий объект не будет уничтожен во время использования клиентом C API.

В следующем фрагменте кода я использую инкремент() и декремент() для управленияshared_ptr.Для простоты этого примера я сохраняю исходный параметрshared_ptr в глобальной переменной.

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/scoped_ptr.hpp>
using namespace std;

typedef boost::shared_ptr<int> MySharedPtr;
MySharedPtr ptr = boost::make_shared<int>(123);

void* increment()
{
    // copy constructor called
    return new MySharedPtr(ptr);
}

void decrement( void* x)
{
    boost::scoped_ptr< MySharedPtr > myPtr( reinterpret_cast< MySharedPtr* >(x) );
}

int main()
{
    cout << ptr.use_count() << endl;
    void* x = increment();
    cout << ptr.use_count() << endl;
    decrement(x);
    cout << ptr.use_count() << endl;

    return 0;
}

Выход:

1
2
1

самый быстрый одновременный безблокировочный менеджер (если вы знаете, что делаете).

template< class T >
class shared_pool
{
public:

    typedef T value_type;
    typedef shared_ptr< value_type > value_ptr;
    typedef value_ptr* lock_handle;

shared_pool( size_t maxSize ):
    _poolStore( maxSize )
{}

// returns nullptr if there is no place in vector, which cannot be resized without locking due to concurrency
lock_handle try_acquire( const value_ptr& lockPtr ) {
    static value_ptr nullPtr( nullptr );
    for( auto& poolItem: _poolStore ) {
        if( std::atomic_compare_exchange_strong( &poolItem, &nullPtr, lockPtr ) ) {             
            return &poolItem;
        }
    }
    return nullptr;
}


lock_handle acquire( const value_ptr& lockPtr ) {
    lock_handle outID;
    while( ( outID = try_acquire( lockPtr ) ) == nullptr ) {
        mt::sheduler::yield_passive(); // ::SleepEx( 1, false );
    }
    return outID;
}

value_ptr release( const lock_handle& lockID ) {
    value_ptr lockPtr( nullptr );
    std::swap( *lockID, lockPtr);
    return lockPtr;
}

protected:

    vector< value_ptr > _poolStore;

};

std::map не такой быстрый, требует дополнительного поиска, дополнительной памяти, спин-блокировки.Но это обеспечивает дополнительную безопасность благодаря использованию ручек.

Кстати, взлом с ручным выпуском/захватом кажется гораздо лучшим подходом (с точки зрения скорости и использования памяти).Стандартно C++ лучше добавить такую ​​функциональность в свои классы, просто чтобы C++ оставался бритвенным.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top