Frage

Gibt es eine Möglichkeit, manuell zu erhöhen bzw. verringern Sie die Zählung eines shared_ptr in C ++?

Das Problem, das ich zu lösen versuche, ist wie folgt. Ich bin eine Bibliothek in C ++ zu schreiben, aber die Schnittstelle intern in reinem C sein, ich möchte Shared_ptr verwenden Speicherverwaltung zu vereinfachen und gleichzeitig die Fähigkeit, die Erhaltung einen Rohzeiger durch die C-Schnittstelle zu übergeben.

Wenn ich einen Rohzeiger über die Schnittstelle passieren, ich möchte den Referenzzähler erhöhen. Der Kunde wird dann verantwortlich sein, eine Funktion aufzurufen, die den Referenzzähler dekrementiert wird, wenn es nicht mehr das übergebene Objekt benötigt.

War es hilfreich?

Lösung

In Ihrem Vorschlag

  

Der Kunde wird dann verantwortlich sein, den Zähler zu verringern.

bedeutet, dass die betreffenden Kunden für die Speicherverwaltung zuständig ist, und dass Ihr Vertrauen sie. Ich verstehe immer noch nicht, warum.

Es ist nicht möglich, tatsächlich die Shared_ptr Zähler zu ändern ... (hum, ich am Ende erklären würde, wie ...), aber es gibt auch andere Lösungen.

Lösung 1: vollständige Eigentum an den Client

Hand über den Zeiger auf dem Client ( Shared_ptr :: Release ) und erwarten, dass es das Eigentum zurück an Sie weitergeben beim Aufruf zurück (oder einfach löschen das Objekt, wenn es nicht wirklich geteilt wird).

Das ist eigentlich der traditionelle Ansatz, wenn sie mit rohen Zeigern und es gilt auch hier tun. Der Nachteil ist, dass Sie tatsächlich Eigentum Release für dieses Shared_ptr nur . Wenn das Objekt ist tatsächlich geteilt , die unbequem erweisen könnten ... so mit mir.

Lösung 2: mit einem Rückruf

Diese Lösung bedeutet, dass Sie immer das Eigentum behalten und sind dafür verantwortlich, diese Aufgabe am Leben (und Tritte), solange der Kunde braucht, um es zu halten. Wenn der Client mit dem Objekt fertig ist, erwarten Sie sie Sie so erzählen und einen Rückruf in Ihrem Code aufrufen, die die notwendige Bereinigung durchführen wird.

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

Auf diese Weise Ihre Klienten Dekrementieren 'der Zähler tatsächlich Ihr Client ist eine Callback-Methode mit der ID ruft Sie verwendet, und Sie löschen ein shared_ptr:)

Hacking boost :: shared_ptr

Wie gesagt es möglich ist (da wir in C ++) zu hacken tatsächlich in die shared_ptr. Es gibt sogar mehr Möglichkeiten, es zu tun.

am besten Art und Weise (und einfachste) ist einfach die Datei zu kopieren unter einem anderen Namen nach unten (my_shared_ptr?) Und dann:

  • ändern die gehören Wachen
  • umfassen die realen Shared_ptr am Anfang
  • umbenennen jede Instanz von shared_ptr mit Ihrem eigenen Namen (und die privaten zu öffentlichen ändern, um die Attribute zuzugreifen)
  • entfernen Sie alle Sachen, die bereits in der realen Datei definiert ist zu vermeiden Kollisionen

So können Sie leicht eine shared_ptr Ihrer eigenen erhalten, für die Sie den Zähler zugreifen können. Es löst nicht das Problem der C-Code direkt mit wenn der Zähler zugreifen, haben Sie möglicherweise zu ‚vereinfachen‘ hier den Code, es zu ersetzen durch eine eingebaute (die, wenn Sie funktioniert nicht multi-threaded, und ist geradezu katastrophal wenn Sie sind).

ich weggelassen absichtlich die ‚reinterpret_cast‘ Trick und die Zeiger Offsets diejenigen. Es gibt so viele Möglichkeiten, Illegit Zugang zu etwas in C / C ++!

zu gewinnen

Darf ich Ihnen nicht raten, obwohl die Hacks zu benutzen? Die beiden Lösungen, die ich oben vorgestellten sollte ausreichen, um Ihr Problem zu lösen.

Andere Tipps

Vielleicht boost Sie verwenden :: shared_ptr accross DLL Grenzen, was nicht richtig funktioniert. In diesem Fall boost :: intrusive_ptr könnte helfen aus. Dies ist ein häufiger Fall von Missbrauch von shared_ptr versuchen, den Menschen mit schmutzigen Hacks umgehen ... Vielleicht soll ich bin falsch in Ihrem Fall, aber es kein guter Grund zu tun, was Sie zu tun versuchen, -)

ADDED 07/2010: Die Probleme scheinen zu kommen aus DLL Laden / Entladen als vom Shared_ptr selbst. Auch die Boost-Logik über die Fälle nicht viel sagen, wenn boost::intrusive_ptr über shared_ptr bevorzugt werden soll. Ich wechselte Entwicklung auf .NET und nicht die Details TR1 zu diesem Thema folgen, also passen Sie diese Antwort nicht gültig sein könnte mehr jetzt ...

1. Ein Griff?

Wenn Sie ein Maximum an Sicherheit wünschen, gibt dem Benutzer einen Griff, nicht der Zeiger. Auf diese Weise gibt es keine Möglichkeit, wird er versuchen, es zu free und Halb erfolgreich zu sein.

ich unter dem nehmen werde, der Einfachheit halber, werden Sie dem Benutzer die Objektzeiger geben.

2. erwerben und unacquire?

Sie sollten einen Manager-Klasse erstellen, wie von Matthieu M. in seinem Antwort , auswendig zu lernen, was / unacquired vom Benutzer erworben wurde.

Wie die inferface C ist, kann man nicht erwarten, dass er delete oder was auch immer verwenden. Also, ein Header wie:

#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

Wird es dem Benutzer ermöglichen Ihre Klasse zu verwenden. Ich habe eine Erklärung eines Dummy-Struktur, weil ich die C Benutzer helfen wollen von ihm nicht die Verwendung des generischen void * Zeiger aufzuzwingen. Aber void * verwendet, ist immer noch eine gute Sache.

Die C ++ Quelle die Funktion der Umsetzung wäre:

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

}

Beachten Sie, dass der Zeiger auf MyStruct Ebene ungültig ist. Sie sollen es aus irgendeinem Grunde nicht verwenden, ohne sie in ihren ursprünglichen MyClass Typen reinterpret_cast-ing (JAIF der beantworten für weitere Informationen über das. die C Benutzer nur mit der zugehörigen MyStruct_ * Funktionen verwendet werden.

Beachten Sie auch, dass dieser Code überprüfen, die Klasse nicht vorhanden ist. Dies könnte übertrieben, aber es ist eine mögliche Verwendung eines Managers (siehe unten)

3. Über den Manager

Der Manager hält, wie von Matthieu M., eine Karte mit dem gemeinsamen Zeiger als Wert (und dem Zeiger selbst, oder dem Griff, als Schlüssel) vorgeschlagen. Oder ein Multimap, wenn es möglich ist, dass der Benutzer irgendwie zu erwerben das gleiche Objekt mehrmals.

Die gute Sache über die Verwendung eines Managers wird sein, dass Ihre C ++ Code in der Lage sein wird, zu verfolgen, welche Objekte waren schmal nicht „unacquired“ richtig durch die Benutzer (Hinzufügen von Informationen in der acquire / unacquire Methoden wie __FILE__ und __LINE__ könnte helfen die Bug-Suche).

So wird der Manager in der Lage sein:

  1. nicht frei ein nicht existierendes Objekt (wie hat der C Benutzer verwalten ein zu erwerben, übrigens?)
  2. am Ende der Ausführung wissen, welche Objekte nicht unaquired waren
  3. Bei unacquired objets, zerstören sie trotzdem (die aus einer RAH Sicht gut ist) Dies ist etwas böse, aber man konnte diese
  4. bieten
  5. Wie oben im Code gezeigt, könnte es sogar einen Zeiger erkennen hilft, verweist nicht auf eine gültige Klasse

Sie sollten hier Trennung von Bedenken tun: wenn der Kunde in einem rohen Zeiger übergibt, wird der Client für die Speicherverwaltung verantwortlich sein (das heißt aufzuräumen danach). Wenn Sie die Zeiger erstellen, werden Sie für die Speicherverwaltung verantwortlich. Dies wird Ihnen auch mit den DLL-Grenze Probleme helfen, die in einer anderen Antwort erwähnt wurden.

Ich kam in einem Anwendungsfall, wo ich so etwas wie dies brauchte, bezogen auf IOCompletionPorts und Gleichzeitigkeit betrifft. Die hacky aber standardkonformes Verfahren ist auf Anwalt es wie von Herb Sutter beschrieben hier .

Der folgende Codeausschnitt ist für std :: shared_ptr wie VC11 umgesetzt:

Impl Datei:

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

Ersetzen Sie [Yourtype] mit der Art des Objekts, das Sie auf die Zählung ändern müssen. Es ist wichtig zu beachten, dass dies ziemlich hacky ist und verwendet plattformspezifische Objektnamen. Die Menge der Arbeit, die Sie gehen müssen, um durch diese Funktionalität zu erhalten, ist wahrscheinlich bezeichnend dafür, wie schlecht eine Idee es ist. Außerdem bin ich Spiele mit dem auto_ptr zu spielen, weil die Funktion Ich bin Hijacking von shared_ptr in einem auto_ptr nimmt.

Eine andere Möglichkeit wäre, nur dynamisch eine Kopie des Shared_ptr zuweisen, um die refcount zu erhöhen, und es freigeben, um sie zu verringern. Dies garantiert, dass mein gemeinsames Objekt nicht während des C-API-Client in Gebrauch vernichtet werden.

Im folgenden Code-Schnipsel, verwende ich Schritt () und Dekrement (), um eine shared_ptr zu steuern. Zur Vereinfachung dieses Beispiels, speichere ich die anfängliche shared_ptr in einer globalen Variablen.

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

Ausgabe:

  

1 |   2
  1

schnellstmöglichen gleichzeitigen lockless Manager (wenn Sie wissen, was Sie tun).

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 ist nicht so schnell, erfordert zusätzliche Such, zusätzliche Speicher, Spin-Verriegelung. Aber es gewährt zusätzliche Sicherheit mit Griffen Ansatz.

BTW, hacken mit manueller Auslösung / acquire scheint ein viel besserer Ansatz (in Bezug auf Geschwindigkeit und Speicherverbrauch) zu sein. C ++ std besser fügen Sie eine solche Funktionalität in ihren Klassen, nur C zu halten ++ rasiermesser geformt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top