2013-06-27 13 views
10

Questa è una stranezza, dove non so, se è con lo standard C++, con il mio compilatore (versione G ++ 4.6.3 su Ubuntu 12.04, che è l'ultima a lungo termine versione supporto di Ubuntu), o con me, che non capisce ;-)std :: swap stranezza con G ++

il codice in questione è tanto semplice quanto segue:

#include <algorithm> // for std::swap 
void f(void) 
{ 
    class MyClass { }; 
    MyClass aa, bb; 
    std::swap(aa, bb);   // doesn't compile 
} 

Quando si cerca di compilare con G ++, il compilatore produce il seguente messaggio di errore:

test.cpp: In function ‘void f()’: 
test.cpp:6:21: error: no matching function for call to ‘swap(f()::MyClass&, f()::MyClass&)’ 
test.cpp:6:21: note: candidates are: 
/usr/include/c++/4.6/bits/move.h:122:5: note: template<class _Tp> void std::swap(_Tp&, _Tp&) 
/usr/include/c++/4.6/bits/move.h:136:5: note: template<class _Tp, long unsigned int _Nm> void std::swap(_Tp (&)[_Nm], _Tp (&)[_Nm]) 

Il risultato sorprendente è che semplicemente spostando la definizione della classe di funzione fa si che il codice compilato bene:

#include <algorithm> // for std::swap 
class MyClass { }; 
void f(void) 
{ 
    MyClass aa, bb; 
    std::swap(aa, bb);   // compiles fine! 
} 

Così è, che std :: swap() non dovrebbe lavorare sulle classi, che sono privato di funzioni? O è un bug con G ++, forse la versione specifica di G ++ che sto usando?

Ancora più sconcertante è che il seguente funziona ancora una volta, nonostante MyListClass è anche privato (ma si estende una classe di "ufficiale", per la quale forse esiste una specifica implementazione di swap()):

#include <algorithm>  // for std::swap 
#include <list>    // for std::list 
void g(void) 
{ 
    class MyListClass : public std::list<int> { }; 
    MyListClass aa, bb; 
    std::swap(aa, bb);    // compiles fine! 
} 

ma basta cambiare da oggetti a puntatori, e la compilazione fallisce di nuovo:

#include <algorithm>  // for std::swap 
#include <list>    // for std::list 
void g(void) 
{ 
    class MyListClass : public std::list<int> { }; 
    MyListClass aa, bb; 
    MyListClass* aap = &aa; 
    MyListClass* bbp = &bb; 
    std::swap(aap, bbp); // doesn't compile! 
} 

Naturalmente, nella mia applicazione reale le classi sono più complesse; Ho semplificato il codice il più possibile per riprodurre ancora il problema.

risposta

16

Se si esegue in modalità C++ 03, che ritengo sia il caso, non è consentito utilizzare un tipo definito localmente in un modello. In questo caso puoi definire i tuoi tipi a livello di spazio dei nomi per farlo funzionare, oppure puoi compilare in modalità C++ 11 dove dovrebbe compilare. [*]

Nel caso ti chiedi perché il secondo caso Works, la norma non prevede specializzazioni di

template <typename T> void swap(T&,T&) // [1] 

come std::list è un modello in sé e non si può in parte specializzarsi funzioni template. Ciò che offre è un modello base diversa:

template <typename T, typename A> void swap(list<T,A>&,list<T,A>&); // [2] 

Ora, come nel caso precedente, il compilatore non può utilizzare il tipo di locale con [1], in modo che viene scartata. Quindi prova [2] e scopre che può convertire il valore del tipo locale in riferimenti alla base std::list<int>, e dopo tale conversione [2] è un buon candidato. Sarà quindi chiamare

std::swap(static_cast<std::list<int&>>(aa),static_cast<std::list<int&>>(bb)); 

che non utilizza un tipo di locale, ma piuttosto il livello di spazio dei nomi std::list<int>.

D'altra parte, il fatto che compili non significa che fa quello che vuoi. In particolare, se il tipo esteso MyListClass aggiunge nuove variabili membro, quelle saranno non scambiate.

Tutto ciò che viene detto, e solo come una nota a margine: non si dovrebbe ereditare da contenitori standard, in quanto non sono stati progettati per essere ereditati da.

[*] Dichiarazione di non responsabilità: non so se questa funzione è supportata in quella particolare versione del compilatore, sarà necessario ricontrollare.

+1

Secondo [il wiki Apache] (http://wiki.apache.org/stdcxx/C%2B%2B0xCompilerSupport), GCC supporta questo dal 4.5, quindi dovrebbe essere sufficiente aggiungere l'opzione '-std = c + + 0x'. – Angew

+0

Grazie mille per le vostre risposte! Aggiungere -std = C++ 0x rende le cose ben compilabili anche con le classi locali. Wow, non sapevo dell'incompatibilità con classi e template locali nei vecchi standard C++. –