Question

Y at-il un moyen d'augmenter manuellement et diminuer le nombre d'un shared_ptr en C ++?

Le problème que je suis en train de résoudre est la suivante. Je suis en train d'écrire une bibliothèque en C ++, mais l'interface doit être dans le plus pur C. En interne, je voudrais utiliser shared_ptr pour simplifier la gestion de la mémoire tout en préservant la capacité de passer un pointeur brut via l'interface C.

Quand je passe un pointeur brut à travers l'interface, je voudrais augmenter le nombre de références. Le client sera alors responsable d'appeler une fonction qui décrémenter le compte de référence quand il n'a plus besoin de l'objet passé.

Était-ce utile?

La solution

Dans votre suggestion

  

Le client sera alors responsable de décrémenter le compteur.

signifie que le client en question est responsable de la gestion de la mémoire, et que votre confiance en elle. Je ne comprends toujours pas pourquoi.

Il est impossible de modifier réellement le compteur shared_ptr ... (hum, je vais vous expliquer à la fin comment ...) mais il existe d'autres solutions.

Solution 1: complète propriété au client

La main sur le pointeur vers le client ( shared_ptr :: version ) et attendre de passer la propriété au moment d'appeler de retour (ou la simple suppression de l'objet si elle est pas vraiment partagé).

C'est en fait l'approche traditionnelle lorsqu'ils traitent avec des pointeurs premières et appliquer ici. L'inconvénient est que vous relâchez réellement la propriété pour cette shared_ptr seulement . Si l'objet est en fait partagé qui pourrait se révéler peu pratique ... donc supporter avec moi.

Solution 2: avec un rappel

Cette solution signifie que vous gardez toujours la propriété et vous êtes responsable de maintenir en vie cet objet (et coups de pied) aussi longtemps que le client a besoin. Lorsque le client se fait avec l'objet, vous vous attendez de vous le dire et appeler un rappel dans votre code qui effectuera le nettoyage nécessaire.

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 cette façon, votre client 'décrémentation du compteur est réellement votre client appelant une méthode de rappel avec l'identifiant que vous avez utilisé, et vous supprimer un shared_ptr:)

Piratage boost :: shared_ptr

Comme je l'ai dit, il est possible (puisque nous sommes en C ++) pour pirater réellement dans le shared_ptr. Il y a même plusieurs façons de le faire.

meilleur façon (et plus facile) est tout simplement de copier le fichier vers le bas sous un autre nom (my_shared_ptr?) Puis:

  • changer les gardes comprennent
  • inclure le vrai shared_ptr au début
  • renomme une instance de shared_ptr avec votre propre nom (et changer le privé au public pour accéder aux attributs)
  • supprimer tous les trucs qui est déjà défini dans le fichier réel pour éviter les affrontements

De cette façon, vous obtenez facilement un shared_ptr de votre propre, pour lequel vous pouvez accéder au comptage. Il ne résout pas le problème d'avoir le code C accéder directement au comptoir cependant, vous pourriez avoir à « simplifier » le code ici pour le remplacer par un haut-(qui fonctionne si vous n'êtes pas multi-thread, et est carrément désastreuse si vous êtes).

J'ai volontairement laissé de côté l'affaire de « reinterpret_cast » et ceux des décalages de pointeur. Il y a tellement de façons de gagner illegit accès à quelque chose en C / C ++!

Je vous conseille de ne pas utiliser les hacks bien? Les deux solutions que je présentées ci-dessus devrait être suffisant pour attaquer votre problème.

Autres conseils

Peut-être que vous utilisez boost :: shared_ptr accross limites DLL, ce qui ne fonctionnera pas correctement. Dans ce cas, boost :: intrusive_ptr pourrait vous aider en dehors. Ceci est un cas commun d'abus de personnes shared_ptr essaient de travailler autour avec hacks sales ... Peut-être que je me trompe dans votre cas, mais il devrait y avoir aucune raison de faire ce que vous essayez de le faire; -)

AJOUTÉE 07/2010: Les problèmes semblent venir plus de chargement / déchargement DLL que du shared_ptr lui-même. Même la raison d'être de boost ne dit pas grand-chose au sujet des cas où boost::intrusive_ptr doit être préféré shared_ptr. Je suis passé au développement .NET et n'a pas suivi les détails de TR1 concernant ce sujet, alors méfiez-vous cette réponse pourrait ne pas être valide plus maintenant ...

1. Une poignée?

Si vous voulez une sécurité maximale, donne à l'utilisateur une poignée, pas le pointeur. De cette façon, il n'y a aucun moyen, il va essayer de free et demi-succès.

Je suppose que ci-dessous, par souci de simplicité, vous allez donner à l'utilisateur le pointeur d'objet.

2. acquérir et unacquire?

Vous devez créer une classe de gestionnaire, tel que décrit par Matthieu M. dans son réponse , de mémoriser ce qui a été acquis / par l'utilisateur non acquise.

En tant que inferface est C, vous ne pouvez pas l'attendre à utiliser delete ou autre chose. Ainsi, un en-tête comme:

#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

permettra à l'utilisateur d'utiliser votre classe. J'ai utilisé une déclaration d'une struct fictive parce que je veux aider l'utilisateur C en ne lui imposant l'utilisation du pointeur de void * générique. Mais l'utilisation void * est toujours une bonne chose.

La source C ++ mise en œuvre de la fonction serait:

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

}

Notez que le pointeur sur MyStruct est clair non valide. Vous ne devriez pas l'utiliser pour une raison quelconque, sans reinterpret_cast-ment dans son type de MyClass d'origine (voir la fiche JAIF Réponse pour plus d'informations sur ce sujet. l'utilisateur C utilisera uniquement avec le MyStruct_ associé * fonctions.

Notez aussi que ce code vérifier la classe existe. Cela pourrait être exagéré, mais il est une utilisation possible d'un gestionnaire (voir ci-dessous)

3. A propos du gestionnaire

Le gestionnaire tiendra, comme suggéré par Matthieu M., une carte contenant le pointeur partagé en tant que valeur (et le pointeur lui-même, ou la poignée, comme la clé). Ou un multimap, s'il est possible pour l'utilisateur d'acquérir une certaine façon plusieurs fois le même objet.

La bonne chose à propos de l'utilisation d'un gestionnaire sera que votre code C ++ sera en mesure de retracer les objets ne sont pas « non acquisition » correctement par l'utilisateur (ajout d'informations dans les méthodes d'acquisition / unacquire comme __FILE__ et __LINE__ pourrait aider à réduire la recherche de bug).

Ainsi, le gestionnaire sera en mesure de:

  1. PAYANT un objet non existant (comment l'utilisateur C a réussi à acquérir une, par la voie?)
  2. savoir à la fin de l'exécution des objets qui ne sont pas unaquired
  3. Dans le cas d'objets non acquisition, de toute façon les détruire (ce qui est bon du point de vue de RAII) C'est un peu mal, mais vous pouvez offrir ce
  4. Comme le montre le code ci-dessus, il pourrait même aider à détecter un pointeur ne pointe pas vers une classe valide

Vous devriez faire la séparation des préoccupations ici: si le client passe un pointeur brut, le client sera responsable de la gestion de la mémoire (à savoir le nettoyage par la suite). Si vous créez les pointeurs, vous serez responsable de la gestion de la mémoire. Cela vous aidera également les problèmes de limites de DLL qui ont été mentionnées dans une autre réponse.

Je suis tombé sur un cas d'utilisation où je ne besoin de quelque chose comme ça, lié à IOCompletionPorts et les préoccupations de concurrence. La méthode conforme à hacky mais les normes est de avocat comme décrit par Herb Sutter ici .

Le code suivant est pour std :: shared_ptr mis en œuvre par VC11:

Impl fichier:

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

Remplacer [YourType] avec le type d'objet que vous devez modifier le compter. Il est important de noter que c'est assez hacky, et utilise les noms d'objets spécifiques de la plate-forme. La quantité de travail que vous devez passer pour obtenir cette fonctionnalité est probablement une indication de la gravité d'une idée qu'il est. De plus, je joue avec des jeux auto_ptr parce que la fonction que je suis Détournement de shared_ptr prend dans un auto_ptr.

Une autre option serait de simplement allouer dynamiquement une copie du shared_ptr, afin d'augmenter le refcount et désallouer pour décrémenter. Cela garantit que mon objet partagé ne sera pas détruite lors de l'utilisation par le client C api.

Dans le fragment de code suivant, j'utilise incrément () et décrémentation () afin de commander un shared_ptr. Pour la simplicité de cet exemple, je stocke la shared_ptr initiale dans une variable 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;
}

Sortie:

  

1
  2
  1

Gestionnaire de lockless concurrent le plus rapide possible (si vous savez ce que vous faites).

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 :: carte n'est pas si rapide, nécessite la recherche, de la mémoire supplémentaire, spin-blocage. Mais il accorde une sécurité supplémentaire à l'approche des poignées.

BTW, pirater avec la version / manuelle semble acquérir une approche beaucoup mieux (en termes de vitesse et utilisation de la mémoire). C ++ std mieux ajouter une telle fonctionnalité dans leurs classes, juste pour garder C ++ en forme de rasoir.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top