Pregunta

¿Hay una manera de incrementar manualmente y disminuir el recuento de un shared_ptr en C ++?

El problema que estoy tratando de resolver es el siguiente. Estoy escribiendo una biblioteca en C ++ pero la interfaz tiene que estar en pura C. Internamente, me gustaría utilizar shared_ptr para simplificar la gestión de memoria preservando al mismo tiempo la capacidad de pasar un puntero prima a través de la interfaz C.

Cuando paso un puntero prima a través de la interfaz, me gustaría incrementar la cuenta de referencia. El cliente será entonces responsable de llamar a una función que va a disminuir el número de referencia cuando ya no se necesita el objeto pasado.

¿Fue útil?

Solución

En su sugerencia

  

El cliente será entonces responsable de disminuir el contador.

significa que el cliente en cuestión es responsable de la gestión de memoria, y que su confianza ella. Todavía no entiendo por qué.

No es posible modificar realmente el contador shared_ptr ... (hum, voy a explicar al final cómo ...) pero hay otras soluciones.

Solución 1: completa la propiedad al cliente

Entregar el puntero al cliente ( shared_ptr :: liberación ) y esperar a que pase la propiedad volver a la hora de volver a llamar (o simplemente eliminar el objeto si no es muy compartida).

Eso es en realidad el enfoque tradicional cuando se trata de punteros primas y se aplican aquí también. La desventaja es que en realidad liberar la propiedad para este shared_ptr única . Si el objeto es en realidad compartían que podrían resultar inconveniente ... así que tengan paciencia conmigo.

Solución 2: con una devolución de llamada

Esta solución significa que siempre mantenga la propiedad y es responsable de mantener vivo este objeto (y patadas) durante el tiempo que el cliente necesita. Cuando el cliente se realiza con el objeto, se espera que ella le diga lo que e invocar una devolución de llamada en el código que se va a realizar la limpieza necesaria.

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

De esta manera, el cliente 'decrementar' el contador es en realidad su cliente llama a un método de devolución de llamada con el id que utilizó, y que la supresión de uno shared_ptr:)

impulso Hackear :: shared_ptr

Como he dicho, es posible (ya que estamos en C ++) para introducirse en realidad en el shared_ptr. Incluso hay varias formas de hacerlo.

La mejor manera (y más fácil) es simplemente copiar el archivo abajo con otro nombre (my_shared_ptr?) Y, a continuación:

  • cambiar la incluyen guardias
  • incluir la shared_ptr real en el inicio
  • cambiar el nombre de cualquier instancia de shared_ptr con su propio nombre (y cambiar el privado a lo público para acceder a los atributos)
  • retire todo el material que ya está definido en el archivo real de evitar enfrentamientos

De esta manera se obtiene fácilmente un shared_ptr de su propia, para lo cual se puede acceder a la cuenta. No resuelve el problema de tener el código C directamente el acceso al contador, sin embargo, puede que tenga que 'simplificar' el código aquí para sustituirlo por un built-in (que funciona si no está multi-hilo, y es francamente desastroso si usted es).

A propósito dejado fuera el truco 'reinterpret_cast' y las compensaciones de puntero. Sólo hay tantas maneras de ganar Illegit acceso a algo en C / C ++!

¿Puedo recomendamos no usar los hacks sin embargo? Las dos soluciones que he presentado anteriormente deberían ser suficientes para hacer frente a su problema.

Otros consejos

Puede que esté utilizando impulso :: shared_ptr al otro lado de las fronteras DLL, lo que no funcionará correctamente. En este caso impulsar :: intrusive_ptr podría ayudarle fuera. Este es un caso común de uso indebido de shared_ptr la gente trata de evitar con cortes sucios ... Tal vez estoy equivocado en su caso, pero no debe haber una buena razón para hacer lo que se intenta hacer; -)

AÑADIDO 07/2010: Los problemas parecen venir más de DLL de carga / descarga que de la misma shared_ptr. Incluso la lógica impulso no le dice mucho acerca de los casos en los que boost::intrusive_ptr se prefiere en shared_ptr. Cambié a .NET desarrollo y no seguir los detalles de TR1 con respecto a este tema, así que ten cuidado con esta respuesta podría no ser válida más ahora ...

1. Un mango?

Si desea la máxima seguridad, le da al usuario un mango, no el puntero. De esta manera, no hay forma de que tratará de free y media éxito.

Vamos a suponer que a continuación, para simplificar, se le da al usuario el puntero del objeto.

2. adquirir y unacquire?

Usted debe crear una clase gerente, según lo descrito por M. Matthieu en su respuesta , para memorizar lo que fue adquirida / no adquirida por el usuario.

A medida que el inferface es C, no se puede esperar que lo utilice delete o lo que sea. Por lo tanto, un encabezado como:

#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

permitirá al usuario utilizar su clase. He utilizado una declaración de una estructura ficticia porque quiero ayudar al usuario C, y no le impone el uso del puntero void * genérico. Pero el uso de void * sigue siendo una buena cosa.

La fuente C ++ la aplicación de la característica sería:

#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) ;
}

}

Tenga en cuenta que el puntero a MyStruct es evidente válido. No se debe utilizar por cualquier motivo y sin reinterpret_cast-ción en su tipo MiClase original (véase de JAIF Respuesta para obtener más información sobre eso. el usuario C se utilizará sólo con el MyStruct_ asociado * funciones.

Tenga en cuenta también que este código verificar qué existe la clase. Esto podría ser una exageración, pero es un posible uso de un gestor (véase más adelante)

3. Sobre el gestor

El director llevará a cabo, según lo sugerido por Matthieu M., un mapa que contiene el puntero compartida como un valor (y el puntero en sí, o el mango, como la clave). O una multimap, si es posible que el usuario adquiera alguna manera el mismo objeto varias veces.

Lo bueno de la utilización de un gerente será que el código C ++ será capaz de rastrear la que los objetos no eran "no adquirida" correctamente por la información del usuario (la adición de las / los métodos unacquire adquieren como __FILE__ y __LINE__ podría ayudar estrecha la búsqueda de errores).

Así, el gerente será capaz de:

  1. NO liberar un objeto no existente (¿cómo el usuario C logró adquirir uno, por cierto?)
  2. saber al final de la ejecución que los objetos no eran unaquired
  3. En el caso de objetos no adquirida, destruirlos todos modos (que es bueno desde el punto de vista RAII) Esto es algo malo, sino que podría ofrecer este
  4. Como se muestra en el código anterior, podría incluso ayudar a detectar un puntero no apunta a una clase válida

Usted debe hacer separación de las preocupaciones aquí: si el cliente pasa un puntero prima, el cliente será responsable de la gestión de memoria (es decir, limpiar después). Si crea los punteros, usted será responsable de la gestión de memoria. Esto también le ayudará con los problemas de límites DLL que se han mencionado en otra respuesta.

Me encontré con un caso de uso donde me hacía falta algo así, relacionada con IOCompletionPorts y preocupaciones de concurrencia. El método compatible con las normas hacky, pero es abogado como se describe por Herb Sutter aquí .

El siguiente fragmento de código es para std :: shared_ptr que se aplica en VC11:

Impl del archivo:

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();
}

Reemplazar [YourType] con el tipo de objeto que resulta necesario modificar el recuento de. Es importante tener en cuenta que esto es bastante hacky, y utiliza los nombres de objetos específicos de la plataforma. La cantidad de trabajo que tiene que pasar para obtener esta funcionalidad es probablemente indicativo de la gravedad de una idea que es. Además, yo estoy jugando con la auto_ptr porque la función que estoy Secuestro de shared_ptr toma en un auto_ptr.

Otra opción sería la de simplemente asignar dinámicamente una copia de la shared_ptr, con el fin de incrementar el refcount y desasignar con el fin de disminuirlo. Esto garantiza que mi objeto compartido no será destruido mientras está en uso por el cliente de la API C.

En el siguiente fragmento de código, utilizo incremento () y decremento () con el fin de controlar un shared_ptr. Para la simplicidad de este ejemplo, guardo el shared_ptr inicial en una variable global.

#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;
}

Salida:

  

1 |   2
  1

más rápida posible gestor de lockless concurrente (si usted sabe lo que está haciendo).

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 :: mapa no es tan rápido, requiere de búsqueda adicional, memoria adicional, spin-bloqueo. Pero otorga mayor seguridad con enfoque de asas.

Por cierto, cortar con desbloqueo manual / adquirir parece ser un enfoque mucho mejor (en términos de velocidad y el uso de memoria). C ++ std mejor añadir dicha funcionalidad en sus clases, sólo para mantener C ++ de afeitar en forma.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top