Buon modello di progettazione per un wrapper c ++ attorno a un oggetto c

7

Ho scritto un wrapper c ++ estendibile attorno a una libreria c molto difficile da usare ma anche molto utile. L'obiettivo è avere la convenienza di c ++ per allocare l'oggetto, esporre le sue proprietà, deallocare l'oggetto, copiare la semantica ecc ...

Il problema è questo: a volte la libreria c vuole l'oggetto sottostante (un puntatore all'oggetto), e il distruttore di classe non dovrebbe distruggere la memoria sottostante. Mentre la maggior parte delle volte, il distruttore dovrebbe deallocare l'oggetto sottostante. Ho sperimentato con l'impostazione di un flag bool hasOwnership nella classe in modo che il distruttore, l'operatore di assegnazione, ecc ... sapesse se deve o meno liberare la memoria sottostante o meno. Tuttavia, questo è ingombrante per l'utente, e inoltre, a volte non c'è modo di sapere quando un altro processo utilizzerà quella memoria.

Attualmente, ho installato dove quando il compito proviene da un puntatore dello stesso tipo del tipo sottostante, quindi ho impostato il flag hasOwnership. Faccio lo stesso quando il costruttore sovraccaricato viene chiamato usando il puntatore dalla libreria c. Tuttavia, questo ancora non gestisce il caso quando l'utente ha creato l'oggetto e lo ha passato ad una delle mie funzioni che chiama c_api e la libreria memorizza il puntatore per un uso successivo. Se dovessero cancellare il loro oggetto, allora senza dubbio causerebbe un segfault nella libreria c.

Esiste un modello di progettazione che semplificherebbe questo processo? Forse una sorta di conteggio dei riferimenti?

    
posta Jonathan Henson 05.01.2013 - 00:24
fonte

4 risposte

4

Se la responsabilità di ripulire le risorse allocate dinamicamente si sposta tra la classe wrapper e la libreria C in base a come le cose vengono utilizzate, si ha a che fare con una libreria C mal progettata, o si sta tentando di fare troppo nel wrapper classe.

Nel primo caso, tutto ciò che puoi fare è tenere traccia di chi è responsabile della pulizia e sperare che non vengano commessi errori (né da te né dai manutentori della libreria C).

Nel secondo caso, dovresti riconsiderare il design del tuo wrapper. Tutte le funzionalità appartengono alla stessa classe o possono essere suddivise in più classi. Forse la libreria C usa qualcosa di simile al pattern di progettazione della facciata e dovresti mantenere una struttura simile nel tuo wrapper C ++.

In ogni caso, anche se la libreria C è responsabile della pulizia di alcune cose, non c'è nulla di sbagliato nel mantenere un riferimento / puntatore a quella roba. Devi solo ricordare che non sei responsabile della pulizia di ciò a cui il puntatore fa riferimento.

    
risposta data 05.01.2013 - 13:06
fonte
3

Spesso puoi usare uno schema come questo:

class C {
public:
  void foo() {
    underlying_foo(handle.get());
  }

  void bar() {
    // transfers ownership
    underlying_bar(handle.release());
  }

  // use default copy/move constructor and assignment operator

private:
  struct deleter {
    void operator()(T* ptr) {
      deleter_fn(ptr);
    }
  };
  std::unique_ptr<T, deleter> handle;
};

Usando release puoi trasferire esplicitamente la proprietà. Tuttavia questo è fonte di confusione e dovresti evitarlo se possibile.

La maggior parte delle librerie C ha un ciclo di vita dell'oggetto simile al C ++ (allocazione di oggetti, accessorie, distruzione) che si adatta perfettamente al modello C ++ senza trasferimento di proprietà.

Se gli utenti hanno bisogno di proprietà condivisa, dovrebbero usare shared_ptr con le tue classi. Non provare a implementare la condivisione della proprietà da solo.

Aggiornamento: se vuoi rendere più esplicito il trasferimento di proprietà, puoi utilizzare un qualificatore di riferimento:

void bar() && { ... }

Quindi gli utenti devono chiamare bar su lvalues come questo:

C o;
std::move(o).bar();  // transfer of ownership is explicit at call site
    
risposta data 05.01.2013 - 04:42
fonte
0

C'è una risposta semplice al tuo problema, puntatori intelligenti. Usando un puntatore intelligente per conservare la memoria della libreria C e aggiungere un riferimento quando il puntatore si trova anche nella libreria (e rilasciare il riferimento quando ritorna la libreria C), si libera automaticamente la memoria quando il conteggio dei riferimenti scende a zero (e solo allora)

    
risposta data 05.01.2013 - 01:55
fonte
0

Se la libreria può liberare le cose internamente e gli scenari in cui ciò può accadere sono ben documentati, allora tutto ciò che puoi fare è impostare una bandiera come già fatto.

    
risposta data 05.01.2013 - 05:01
fonte

Leggi altre domande sui tag