Domanda

C'è un modo per incrementare manualmente e diminuire il conteggio di uno shared_ptr in C ++?

Il problema che sto cercando di risolvere è la seguente. Sto scrivendo una libreria in C ++, ma l'interfaccia deve essere in puro C. Internamente, vorrei utilizzare shared_ptr per semplificare la gestione della memoria, preservando la possibilità di passare un puntatore prima attraverso l'interfaccia C.

Quando passo un puntatore non elaborato tramite l'interfaccia, desidero incrementare il conteggio di riferimento. Il cliente sarà quindi responsabile per chiamare una funzione che diminuire il conteggio di riferimento quando non è più necessario l'oggetto passato.

È stato utile?

Soluzione

Il tuo suggerimento

  

Il cliente sarà quindi responsabile per decrementare il contatore.

significa che il cliente in questione è responsabile per la gestione della memoria, e che la fiducia lei. Io ancora non capisco perché.

Non è possibile modificare in realtà il contatore shared_ptr ... (hum, spiegherò alla fine, come ...), ma ci sono altre soluzioni.

Soluzione 1: la proprietà completa al cliente

Consegnare il puntatore al client ( shared_ptr :: rilascio ) e si aspettano di passare la proprietà di nuovo voi quando si chiama di nuovo (o semplicemente cancellando l'oggetto se non è realmente condivisa).

Che in realtà è l'approccio tradizionale quando si tratta di puntatori prime e si applicano anche qui. Il rovescio della medaglia è che in realtà si rilascia la proprietà per questo shared_ptr solo . Se l'oggetto è in realtà condiviso che potrebbe rivelarsi scomodo ... in modo da portare con me.

Soluzione 2: con un callback

Questa soluzione significa che si mantiene sempre la proprietà e responsabilità di mantenere questo oggetto vivo (e vegeta) per tutto il tempo il cliente ha bisogno. Quando il client è fatto con l'oggetto, ci si aspetta che lei dica così e invocare la richiamata nel codice che eseguirà la pulizia necessaria.

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

In questo modo, il vostro cliente 'decremento' il contatore è in realtà il vostro cliente chiamando un metodo di callback con l'id hai usato, e l'eliminazione di uno shared_ptr:)

impulso Hacking :: shared_ptr

Come ho già detto è possibile (visto che siamo in C ++) per incidere realmente nella shared_ptr. Ci sono anche diversi modi per farlo.

Il più modo (e più semplice) è semplicemente quello di copiare il file verso il basso con un altro nome (my_shared_ptr?) E poi:

  • cambiare il includono guardie
  • comprendere il vero shared_ptr all'inizio
  • rinominare qualsiasi istanza di shared_ptr con il proprio nome (e cambiare il privato al pubblico per accedere agli attributi)
  • rimuovere tutto il materiale che è già definita nel file reale per evitare scontri

In questo modo si ottiene facilmente uno shared_ptr della propria, per la quale è possibile accedere al conteggio. Non risolve il problema di avere il codice C direttamente l'accesso al banco però, potrebbe essere necessario 'semplificare' il codice qui per sostituirlo con un built-in (che funziona se non si è multi-thread, ed è assolutamente disastrose se siete).

Ho volutamente lasciato fuori il trucco 'reinterpret_cast' e quelle offset puntatore. Ci sono così tanti modi per guadagnare illegit accesso a qualcosa in C / C ++!

Posso consigliarvi di non utilizzare i hack però? Le due soluzioni che ho presentato in precedenza dovrebbe essere sufficiente per affrontare il problema.

Altri suggerimenti

Forse si sta utilizzando boost :: shared_ptr attraversato confini DLL, ciò potrebbe non funzionare correttamente. In questo caso boost :: intrusive_ptr potrebbe aiutare su. Questo è un caso comune di abuso di shared_ptr persone cercano di aggirare con hack sporco ... Forse mi sbaglio nel tuo caso, ma non ci dovrebbe essere una buona ragione per fare quello che si cerca di fare; -)

AGGIUNTO 07/2010: I problemi sembrano venire più da DLL di carico / scarico che dal shared_ptr stesso. Anche la logica spinta non dice molto circa i casi in cui boost::intrusive_ptr dovrebbe essere preferito su shared_ptr. Sono passato a .NET sviluppo e non ho seguito i dettagli di TR1 per quanto riguarda questo argomento, quindi attenzione questa risposta potrebbe non essere più valido oggi ...

1. Una maniglia?

Se volete il massimo della sicurezza, offre all'utente una maniglia, non il puntatore. In questo modo, non c'è modo egli cercherà di free e mezza avere successo.

darò per scontato che qui di seguito, per semplicità, si danno all'utente il puntatore all'oggetto.

2. acquisire e unacquire?

Si dovrebbe creare una classe dirigente, come descritto da Matthieu M. nella sua risposta , per memorizzare ciò che è stato acquisito / unacquired dall'utente.

Come l'inferface è C, non si può pretendere che lui usi delete o qualsiasi altra cosa. Quindi, un colpo di testa come:

#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

consentirà all'utente di utilizzare la classe. Ho usato una dichiarazione di una struttura fittizia perché voglio aiutare l'utente C da non gli imporre l'uso del puntatore void * generico. Ma usando void * è ancora una buona cosa.

La sorgente C ++ implementazione della funzione potrebbe essere:

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

}

Si noti che il puntatore a MyStruct è chiaro valido. Si consiglia di non usarlo per qualsiasi ragione senza reinterpret_cast-ing in suo tipo MyClass originale (vedi di Jaif rispondere per maggiori informazioni su questo. l'utente C lo userà solo con il MyStruct_ associato * funzioni.

Si noti anche che questo codice verificare la classe esiste. Questo potrebbe essere eccessivo, ma è un possibile utilizzo di un manager (vedi sotto)

3. Circa il manager

Il manager si terrà, come suggerito da Matthieu M., una mappa che contiene il puntatore condiviso come un valore (e il puntatore stesso, o la maniglia, come la chiave). O un multimap, se è possibile per l'utente di acquisire in qualche modo lo stesso oggetto più volte.

La cosa buona circa l'uso di un manager sarà che il codice C ++ sarà in grado di tracciare quali oggetti non erano "unacquired" correttamente le informazioni utente (aggiungendo nelle acquisire / metodi unacquire come __FILE__ e __LINE__ potrebbe aiutare stretta la ricerca di bug).

In questo modo il manager sarà in grado di:

  1. NON liberare un oggetto inesistente (come ha fatto l'utente C è riuscito ad acquistare uno, tra l'altro?)
  2. CONOSCERE al termine dell'esecuzione quale gli oggetti non erano unaquired
  3. In caso di oggetti unacquired, distruggerli in ogni caso (che è buono dal punto di vista Raii) Questo è un po 'male, ma si potrebbe offrire questo
  4. Come mostrato nel codice di cui sopra, potrebbe anche aiutare a rilevare un puntatore non punta a una classe valida

Si dovrebbe fare la separazione degli interessi qui: se il cliente passa un puntatore RAW, il cliente sarà responsabile per la gestione della memoria (cioè ripulire in seguito). Se si creano i puntatori, sarà responsabile per la gestione della memoria. Questo vi aiuterà anche con le questioni di confine DLL che sono stati menzionati in un'altra risposta.

mi sono imbattuto in un caso d'uso in cui avevo bisogno di qualcosa di simile, relative al IOCompletionPorts e preoccupazioni di concorrenza. Il metodo conforme hacky ma standard è quello di avvocato come descritto da Herb Sutter qui .

Il seguente frammento di codice è per std :: shared_ptr come attuato dalla VC11:

File Impl:

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

Sostituire [YourType] con il tipo di oggetto è necessario modificare il conteggio su. E 'importante notare che questo è abbastanza hacky, e usa i nomi degli oggetti piattaforma specifica. La quantità di lavoro che deve passare attraverso per ottenere questa funzionalità è probabilmente indicativo di quanto male di un'idea che è. Inoltre, sto giocando i giochi con l'auto_ptr perché la funzione che sto dirottamento da shared_ptr prende in un auto_ptr.

Un'altra opzione sarebbe quella di allocare dinamicamente solo una copia del shared_ptr, al fine di incrementare il refcount, e rilasciare al fine di diminuirlo. Questo garantisce che il mio oggetto condiviso non sarà distrutta durante l'uso da parte del cliente C api.

Nel seguente frammento di codice, io uso incremento () e decremento () per controllare uno shared_ptr. Per la semplicità di questo esempio, memorizzare lo shared_ptr iniziale in una variabile globale.

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

Output:

  

1
  2
  1

più veloce possibile responsabile lockless concomitante (se si sa cosa si sta facendo).

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 :: mappa non è così veloce, richiede ulteriore ricerca, memoria aggiuntiva, spin-bloccaggio. Ma garantisce maggiore sicurezza con l'approccio maniglie.

A proposito, hack con sblocco manuale / acquisire sembra essere un approccio molto migliore (in termini di velocità e utilizzo della memoria). C ++ std meglio aggiungere un tale funzionalità nelle loro classi, solo per mantenere C ++ a forma di rasoio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top