2010-07-22 10 views
5

Sto lavorando su una base di codice C++ legacy molto grande che deve rimanere senza nome. Essendo una base di codice legacy, passa i puntatori grezzi in giro dappertutto. Ma stiamo gradualmente cercando di modernizzarlo e quindi ci sono anche alcuni modelli di puntatori intelligenti. Questi puntatori intelligenti (a differenza, per esempio, di scope_ptr di Boost) hanno una conversione implicita al puntatore raw, in modo che sia possibile passare uno di essi in una routine che accetta un puntatore raw senza dover scrivere .get(). Un grosso svantaggio di questo è che si può anche utilizzarne accidentalmente uno in un'istruzione delete, e quindi si ha un doppio bug libero, che può essere un vero problema da rintracciare.Modello di "puntatore intelligente" C++ che si converte automaticamente in puntatore nuda ma non può essere eliminato in modo esplicito

C'è un modo per modificare il modello in modo che abbia ancora la conversione implicita al puntatore raw, ma causa un errore di compilazione se utilizzato in un'istruzione delete? Come questo:

#include <my_scoped_ptr> 

struct A {}; 
extern void f(A*); 

struct B 
{ 
    scoped_ptr<A> a; 

    B(); 
    ~B(); 
}; 

B::B() 
    : a(new A) 
{ 
    f(a); // this should compile 
} 

B::~B() 
{ 
    delete a; // this should NOT compile 
} 

risposta

7

Lo Standard dice

L'operando deve avere un tipo di puntatore, o di un tipo di classe che ha una sola funzione di conversione (12.3.2) per un tipo di puntatore. Se l'operando ha un tipo di classe, l'operando viene convertito in un tipo di puntatore chiamando la funzione di conversione sopra menzionata e l'operando convertito viene utilizzato al posto dell'operando originale per il resto di questa sezione.

È possibile (ab) -utilizzare l'assenza di risoluzione di sovraccarico dichiarando una versione const della funzione di conversione. Su un compilatore conforme che è abbastanza per farlo non funziona più con delete:

struct A { 
    operator int*() { return 0; } 
    operator int*() const { return 0; } 
}; 

int main() { 
    A a; 
    int *p = a; // works 
    delete a; // doesn't work 
} 

risultati nel seguente

[[email protected] cpp]$ clang++ main1.cpp 
main1.cpp:9:3: error: ambiguous conversion of delete expression of type 'A' to a pointer 
    delete a; // doesn't work 
^ ~ 
main1.cpp:2:3: note: candidate function    
    operator int*() { return 0; } 
^
main1.cpp:3:3: note: candidate function    
    operator int*() const { return 0; } 
^
1 error generated. 

su compilatori che sono meno conforme a tale riguardo (EDG/Comeau, GCC) puoi trasformare la funzione di conversione in un modello. delete non si aspetta un tipo particolare , quindi questo dovrebbe funzionare:

template<typename T> 
operator T*() { return /* ... */ } 

Tuttavia, questo ha il rovescio della medaglia che il vostro smart pointer è ora convertibile in qualsiasi puntatore-tipo. Anche se la conversione effettiva è ancora digitata, ma ciò non escluderà le conversioni in anticipo, ma piuttosto darà un errore in fase di compilazione molto più tardi. Purtroppo, SFINAE non sembra essere possibile con funzioni di conversione in C++ 03 :) Un modo diverso è quello di restituire un puntatore privato tipo annidato dalla altra funzione

struct A { 
    operator int*() { return 0; } 

private: 
    struct nested { }; 
    operator nested*() { return 0; } 
}; 

L'unico problema ora è con una conversione a void*, nel qual caso entrambe le funzioni di conversione sono ugualmente valide. Un work-around suggerito da @Luther è di restituire un tipo di puntatore a funzione dall'altra funzione di conversione, che funziona con GCC e Comeau e si sbarazza del problema void* senza avere altri problemi sui soliti percorsi di conversione, a differenza della soluzione template

struct A { 
    operator int*() { return 0; } 

private: 
    typedef void fty(); 
    operator fty*() { return 0; } 
}; 

Si noti che queste soluzioni alternative sono necessarie solo per i compilatori che non sono conformi, però.

+0

Lo faresti, vero? – GManNickG

+0

g ++ è indeciso, 4.4 non si compila, 4.5 lo fa. Se cambio il tipo della seconda conversione in un tipo di puntatore a funzione, g ++ lo fa correttamente e si ferma su 'delete a'. –

+0

@Luther grazie, aggiunto :) –

1

È possibile utilizzare una tecnica presentata da Boost, ma la mia preoccupazione è che si sta permettendo conversioni implicite da un puntatore intelligente a un puntatore grezzo, che è generalmente malvista su. Inoltre, gli utenti possono chiamare delete su un puntatore ottenuto dall'operatore ->, quindi non c'è davvero nulla che tu possa fare per impedire a un determinato idiota di aggirare il meccanismo che ti viene in mente.

si dovrebbe davvero solo implementare un metodo get() invece di fornire operator T*() in modo che almeno chiama a delete smartptr non verrà compilato. I non idioti dovrebbero essere in grado di capire che probabilmente c'è una ragione per cui questo non funzionerà.

Sì, è più lavoro di digitare LegacyFunc(smartptr.get()) di LegacyFunc(smartptr), ma il primo è preferibile dal momento che rende esplicita e previene le conversioni inaspettate accada, come delete smartptr.

Che cosa succede se si dispone di funzioni come questa:

void LegacyOwnPointer(SomeType* ptr); 

dove la funzione memorizzerà il puntatore da qualche parte? Ciò rovinerà il puntatore intelligente, perché ora non è consapevole del fatto che qualcun altro possiede il puntatore raw.

In entrambi i casi, hai del lavoro da fare. I puntatori intelligenti sono come indicatori grezzi, ma non sono la stessa cosa, quindi non puoi semplicemente trovare e sostituire tutte le istanze di T* e sostituirla con my_scoped_ptr<T> e aspettarti che funzioni altrettanto bene come prima.

+0

Questo è un altro problema. – GManNickG

+0

Indipendentemente dal fatto che non sia disapprovato, sarebbe inutile provare a inserire puntatori intelligenti in questo codice legacy senza una conversione implicita al puntatore raw. Ci sono troppe posizioni in cui è necessario fornire il puntatore raw. - Il tuo suggerimento aiuta solo se posso modificare la classe puntata, che non posso, in generale. Ho bisogno di una tecnica che funzioni interamente all'interno della classe del puntatore intelligente. – zwol

+1

Non credo davvero che apprezziate la realtà di lavorare su una base di codice vecchia di 3 milioni di righe e 15 anni. Se prendessi la conversione implicita dal modello di puntatore intelligente (che è già molto usato), dovrei inserire * migliaia * di chiamate .get() e i miei colleghi mi lanceranno. – zwol

4

Non c'è un modo per fermarne uno e non l'altro. Ovunque può essere convertito implicitamente in un puntatore per una chiamata di funzione, può essere convertito implicitamente per un'espressione di eliminazione.

La soluzione migliore è rimuovere la funzione di conversione. La tua situazione è esattamente la ragione per cui gli operatori di conversione definiti dall'utente sono pericolosi e non dovrebbero essere usati spesso.


I'm wrong. :(

+0

Vedere la mia risposta a "In silicio". Sono a conoscenza degli argomenti contro le conversioni implicite, ma semplicemente non è pratico fare a meno di loro in questo contesto. – zwol

+0

@Zack: Quindi non dovrai cancellarli. Questa è la tua scelta: rimuovi la conversione implicita e usa esplicitamente 'get()' per le funzioni, o lascia conversioni implicite e spero che delete non venga chiamato su di esso. – GManNickG

+0

Temo che tu abbia ragione, ma lascerò la domanda aperta per un giorno circa nel caso qualcuno si accorga di qualcosa di intelligente. – zwol

0

non ho pensato molto a questo ma ... Puoi fornire un overload per l'eliminazione dell'operatore che è fortemente tipizzato per le istanze della classe template in modo tale che quando il codice è incluso la compilazione fallisce? se questo è nel file di intestazione, è necessario impedire la conversione implicita nelle chiamate da eliminare a favore di una chiamata al sovraccarico.

operatore delete (my_scoped_ptr) {// ... codice uncompilable va qui }

Mi scuso se questa si rivela essere un'idea stupida.

+0

No, verrebbe invocato per le espressioni di "elimina puntatore puntatore-intelligente", ma non per "elimina puntatore intelligente" che causa il problema. –

0

Posso vedere dove non si vuole fare una massiccia applicazione di .get(). Hai mai considerato una sostituzione molto più piccola di delete?

struct A 
{ 
    friend static void Delete(A* p) { delete p; } 

private: 
    ~A(){} 
}; 

struct B 
{ 
}; 

int main() 
    { 

    delete new B(); //ok 

    Delete(new A); //ok 

    delete new A; //compiler error 

    return (0); 
    } 
+0

Questo aiuta solo se posso modificare la classe puntata, che non posso in generale. – zwol

Problemi correlati