Pergunta

Existe uma maneira de incrementar manualmente e diminuir a contagem de um shared_ptr em C ++?

O problema que eu estou tentando resolver é o seguinte. Eu estou escrevendo uma biblioteca em C ++, mas a interface tem que ser em puro C. Internamente, eu gostaria de usar shared_ptr ao gerenciamento de memória simplificar, preservando a capacidade de passar um ponteiro bruto através da interface C.

Quando eu passar um ponteiro bruto através da interface, eu gostaria de incrementar a contagem de referência. O cliente, em seguida, ser responsável para chamar uma função que irá diminuir a contagem de referência quando já não precisa do objeto passado.

Foi útil?

Solução

Na sua sugestão

O cliente, então, ser responsável para diminuir o contador.

significa que o cliente em questão é responsável por gerenciamento de memória, e que a sua confiança dela. Eu ainda não entendo o porquê.

Não é possível realmente modificar o contador shared_ptr ... (hum, eu vou explicar no final como ...), mas existem outras soluções.

Solução 1: a posse completa para o cliente

Mão sobre o ponteiro para o cliente ( shared_ptr :: liberação ) e esperar que ele passe a volta a propriedade para você ao chamar de volta (ou simplesmente excluir o objeto se não for realmente compartilhado).

Isso é realmente a abordagem tradicional quando se lida com ponteiros crus e aplicam aqui também. A desvantagem é que você realmente liberar a propriedade para este shared_ptr única . Se o objeto é realmente compartilhada , que pode revelar inconveniente ... assim urso comigo.

Solução 2: com um callback

Este meio de soluções que você mantenha sempre propriedade e são responsáveis ??para manter esse objeto vivo (e chutando) durante o tempo que o cliente precisa. Quando o cliente é feito com o objeto, você espera que ela lhe dizer isso e invocar uma chamada de retorno em seu código que irá executar a limpeza necessária.

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

Desta forma, o seu cliente 'decrementing' o contador é realmente seu cliente chamar um método de retorno de chamada com o ID que você usou, e você excluir um shared_ptr:)

Hacking boost :: shared_ptr

Como eu disse, é possível (uma vez que estamos em C ++) para realmente invadir o shared_ptr. Há ainda várias maneiras de fazer isso.

O melhor forma (e mais fácil) é simplesmente copiar o arquivo para baixo com outro nome (my_shared_ptr?) E, em seguida:

  • mudar a incluir guardas
  • incluir o shared_ptr real no início
  • renomear qualquer instância de shared_ptr com seu próprio nome (e mudar o privado ao público para acessar os atributos)
  • remover todo o material que já está definido no arquivo real para evitar confrontos

Desta forma, você obter facilmente um shared_ptr de sua preferência, para o qual você pode acessar a contagem. Não resolve o problema de ter o código C diretamente acessando o balcão, porém, você pode ter que 'simplificar' o código aqui para substituí-lo por um built-in (que funciona se você não é multi-threaded, e é francamente desastrosa Se você é).

Eu propositadamente deixado de fora o truque 'reinterpret_cast' e os deslocamentos ponteiro queridos. Há apenas tantas maneiras de ganhar acesso Illegit para algo em C / C ++!

Maio Eu aconselho a não usar os hacks embora? As duas soluções I apresentados acima deve ser suficiente para resolver o seu problema.

Outras dicas

Talvez você estiver usando boost :: shared_ptr accross limites DLL, que não vai funcionar corretamente. Neste caso boost :: intrusive_ptr pode ajudá-lo Fora. Este é um caso comum de desvio de shared_ptr pessoas tentam contornar com hacks sujos ... Talvez eu esteja errado no seu caso, mas deve haver nenhuma boa razão para fazer o que você tentar fazer; -)

ADICIONADO 07/2010: As questões parecem vir mais de DLL carga / descarga do que do próprio shared_ptr. Mesmo o impulso racional não diz muito sobre os casos em que boost::intrusive_ptr deve ser preferido sobre shared_ptr. Mudei para o desenvolvimento .NET e não seguiu os detalhes de TR1 sobre este tema, por isso tome cuidado esta resposta pode não ser mais válido agora ...

1. Um identificador?

Se você quer segurança máxima, dá ao usuário uma alça, não o ponteiro. Desta forma, não há nenhuma maneira ele vai tentar free-lo e meia sucesso.

Eu vou assumir abaixo disso, para simplificar, você vai dar ao usuário o ponteiro do objeto.

2. adquirir e unacquire?

Você deve criar uma classe de gerente, como descrito por Matthieu M. em sua resposta , para memorizar o que foi adquirida / não adquirido pelo usuário.

Como o inferface é C, você não pode esperar que ele use delete ou qualquer outra coisa. Assim, um cabeçalho 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á ao usuário usar sua classe. Eu usei uma declaração de uma struct manequim, porque eu quero ajudar o usuário C por não impor-lhe o uso do ponteiro void * genérico. Mas usando void * ainda é uma coisa boa.

A fonte C ++ implementar o recurso seria:

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

}

Note que o ponteiro para MyStruct é inválido simples. Você não deve usá-lo por qualquer motivo, sem reinterpret_cast ing-lo em seu tipo MyClass originais (ver do Jaif resposta para mais informações sobre isso. o usuário C vai usá-lo apenas com o MyStruct_ associadas * funções.

Note também que este código verificar a classe não existe. Isso poderia ser um exagero, mas é uma possibilidade de utilização de um gerente (ver abaixo)

3. Sobre o gerente

O gerente irá realizar, como sugerido por Matthieu M., um mapa contendo o ponteiro compartilhada como um valor (eo ponteiro em si, ou a alça, como a chave). Ou um multimap, se é possível para o usuário de alguma forma adquirir o mesmo objeto várias vezes.

A coisa boa sobre o uso de um gerente será que o seu código C ++ será capaz de rastrear quais objetos não eram "não adquirido" corretamente pelo usuário (adicionando informações na adquirida / métodos unacquire como __FILE__ e __LINE__ poderia ajudar estreita a busca bug).

Assim, o gerente será capaz de:

  1. NÃO libertar um objecto não existente (como é que o usuário C conseguiu uma aquisição, pelo caminho?)
  2. KNOW no final de execução que objetos não eram unaquired
  3. Em caso de objectos não adquirido, destruí-los de qualquer maneira (o que é bom do ponto de vista RAII) Isso é um pouco mal, mas você poderia oferecer este
  4. Como mostrado no código acima, que poderia até mesmo ajudar a detectar um ponteiro não aponta para uma classe válida

Você deve fazer separação de interesses aqui: se o cliente passa em um ponteiro bruto, o cliente será responsável pelo gerenciamento de memória (ou seja, limpar depois). Se você criar os ponteiros, você será responsável pelo gerenciamento de memória. Isso também irá ajudá-lo com as questões de fronteira DLL que foram mencionados em outra resposta.

me deparei com um caso de uso onde fiz necessidade algo assim, relacionada com IOCompletionPorts e preocupações de concorrência. O método compatível hacky mas os padrões é a advogado -lo como descrito por Herb Sutter aqui .

O seguinte trecho de código é para std :: shared_ptr como implementado por VC11:

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

Substitua [seuTipo] com o tipo de objeto que você precisa para modificar a contar. É importante notar que este é bastante hacky, e usa plataforma nomes de objeto específico. A quantidade de trabalho que você tem que percorrer para obter essa funcionalidade é provavelmente indicativo de quão ruim de uma idéia que é. Além disso, estou jogando jogos com a auto_ptr porque a função que estou seqüestro de shared_ptr leva em uma auto_ptr.

Outra opção seria apenas para alocar dinamicamente uma cópia do shared_ptr, a fim de incrementar o refcount e desalocar-lo, a fim de diminuí-lo. Isso garante que o meu objeto compartilhado não será destruída enquanto em uso pelo cliente C API.

No seguinte trecho de código, eu uso incremento () e decremento (), a fim de controlar um shared_ptr. Para a simplicidade deste exemplo, eu armazenar o shared_ptr inicial em uma variável 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;
}

Output:

1 | 2
1

mais rápido possível gerente lockless concorrente (se você sabe o que está fazendo).

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 não é tão rápido, requer pesquisa adicional, memória extra, spin-bloqueio. Mas garante maior segurança com alças abordagem.

BTW, corte com o manual de liberação / adquirir parece ser uma melhor abordagem muito (em termos de velocidade e uso de memória). C ++ std melhor adicionar tal funcionalidade em suas aulas, apenas para manter C ++ em forma de navalha.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top