Sto lavorando con una API C legacy in base alla quale l'acquisizione di alcune risorse è costosa e la liberazione di tale risorsa è assolutamente fondamentale. Sto usando C++ 14 e voglio creare una classe per gestire queste risorse. Ecco lo scheletro di base della cosa ...C++ Move Semantics - Wrapping Legacy C API
class Thing
{
private:
void* _legacy;
public:
void Operation1(...);
int Operation2(...);
string Operation3(...);
private:
Thing(void* legacy) :
_legacy(legacy)
{
}
};
Questo non è proprio il modello singleton. Nulla è statico e potrebbero esserci molte istanze di Thing
, tutte gestendo le proprie risorse legacy. Inoltre, questo non è semplicemente un puntatore intelligente. Il puntatore impacchettato, _legacy
è privato e tutte le operazioni sono esposte tramite alcune funzioni di istanza pubbliche che nascondono l'API legacy dai consumatori.
Il costruttore è privata perché le istanze di Thing
saranno restituiti da una fabbrica statica o chiamato costruttore che effettivamente acquisire la risorsa. Ecco un'imitazione a basso costo di quella fabbrica, utilizzando malloc()
come segnaposto per il codice che richiamare le vecchie API ...
public:
static Thing Acquire()
{
// Do many things to acquire the thing via the legacy API
void* legacy = malloc(16);
// Return a constructed thing
return Thing(legacy);
}
Ecco il distruttore, che è responsabile di liberare la risorsa legacy, ancora una volta, free()
è solo un segnaposto ...
~Thing() noexcept
{
if (nullptr != _legacy)
{
// Do many things to free the thing via the legacy API
// (BUT do not throw any exceptions!)
free(_legacy);
_legacy = nullptr;
}
}
Ora, voglio far sì che esattamente una risorsa legacy è gestito da esattamente un'istanza di Thing
. Non volevo che i consumatori della classe Thing
passassero le istanze in giro a piacimento: devono essere di proprietà locale della classe o della funzione, direttamente o tramite unique_ptr
o incapsulati con uno shared_ptr
che può essere passato circa. A tal fine, ho cancellato l'operatore di assegnazione e copiare i costruttori ...
private:
Thing(Thing const&) = delete;
void operator=(Thing const&) = delete;
Tuttavia, questo ha aggiunto un ulteriore problema. O ho dovuto cambiare il mio metodo di fabbrica per restituire uno unique_ptr<Thing>
o uno shared_ptr<Thing>
o ho dovuto implementare la semantica del movimento. Non volevo dettare il modello in base al quale Thing
dovrebbe essere usato così ho scelto di aggiungere una mossa-costruttore e spostare-assegnazione-operator come segue ...
Thing(Thing&& old) noexcept : _legacy(old._legacy)
{
// Reset the old thing's state to reflect the move
old._legacy = nullptr;
}
Thing& operator= (Thing&& old) noexcept
{
if (&old != this)
{
swap(_legacy, old._legacy);
}
return (*this);
}
Con questo tutto fatto, ho potuto utilizzare Thing
come un locale e spostarla su ...
Thing one = Thing::Acquire();
Thing two = move(one);
non ho potuto rompere il modello per il tentativo di commettere auto-assegnazione:
Thing one = Thing::Acquire();
one = one; // Build error!
I Coul d anche fare una unique_ptr
a uno ...
auto three = make_unique<Thing>(Thing::Acquire());
O un shared_ptr
...
auto three = make_shared<Thing>(Thing::Acquire());
Tutto ha funzionato come mi aspettavo e il mio distruttore corse esattamente al momento giusto in tutte le mie prove. In effetti, l'unica irritazione era che make_unique
e sia in realtà invocato il costruttore di movimento - non era ottimizzato come avevo sperato.
Prima domanda: Avere ho implementato il mossa -constructor e Move-assegnazione-operatore correttamente? (Sono abbastanza nuovi per me e questa sarà la prima volta che ne ho usato uno in rabbia.)
Seconda domanda: Si prega di commentare questo modello! È un buon modo per racchiudere le risorse legacy in una classe C++ 14?
Infine: Devo modificare qualcosa per rendere il codice migliore, più veloce, più semplice o più leggibile?
È possibile utilizzare sempre il [modello Singleton statico] (http://stackoverflow.com/questions/1008019/c-singleton-design-pattern) per evitare l'uso di puntatori intelligenti. – NathanOliver
Non è davvero un singleton - solo uno pseudo-singleton. Potrebbero esserci diverse cose, tutte acquisite separatamente ma gestite da un'istanza di 'Thing' ciascuna. – Xharlie
Sembra che si stia reinventando un puntatore intelligente e quindi lo si memorizzi in un altro ointer intelligente. Vorrei solo scrivere un deleter personalizzato e usare direttamente 'std :: unique_ptr' o' std :: shared_ptr'. – Galik