2013-05-07 14 views
15

Non è una buona pratica usare le classi stl nell'interfaccia dll come spiega Common practice in dealing with warning c4251: class … needs to have dll-interface. Un esempio è dato:Un modo per eliminare l'avviso C4251 quando si usano le classi stl nell'interfaccia dll

#include <iostream> 
#include <string> 
#include <vector> 


class __declspec(dllexport) HelloWorld 
{ 
public: 
    HelloWorld() 
    { 
     abc.resize(5); 
     for(int i=0; i<5; i++) 
      abc[i] = i*10; 

     str="hello the world"; 
    } 
    ~HelloWorld() 
    { 

    } 

    std::vector<int> abc; 
    std::string str; 

}; 

Quando si compila questo file, le seguenti avvertenze si possono osservare:

warning C4251: 'HelloWorld::str' : class 'std::basic_string<_Elem,_Traits,_Ax>' needs to have dll-interface to be used by clients of class 'HelloWorld'  
warning C4251: 'HelloWorld::abc' : class 'std::vector<_Ty>' needs to have dll-interface to be used by clients of class 'HelloWorld' 

Allora la domanda è: come possiamo implementare la stessa funzionalità senza utilizzare classe STL vettoriale e stringa. Un'implementazione ho potuto pensare è la seguente:

class __declspec(dllexport) HelloWorld2 
{ 
public: 
    HelloWorld2() 
    { 
     abc_len = 5; 
     p_abc = new int [abc_len]; 
     for(int i=0; i<abc_len; i++) 
      p_abc[i] = i*10; 

     std::string temp_str("hello_the_world"); 
     str_len = temp_str.size(); 
     p_str = new char[str_len+1]; 
     strcpy(p_str,temp_str.c_str()); 
    } 
    ~HelloWorld2() 
    { 
     delete []p_abc; 
     delete []p_str; 
    } 

    int *p_abc; 
    int abc_len; 
    char *p_str; 
    int str_len; 



}; 

Come si può vedere, nella nuova implementazione usiamo int * p_abc per sostituire vettore abc e char * p_str per sostituire string str. La domanda che ho è se ci sono altri eleganti approcci di implementazione che possono fare lo stesso. Grazie!

risposta

19

penso che hai almeno tre possibili opzioni per risolvere questo problema:

  • si potrebbe ancora continuare a classi STL/template per i campi e utilizzarli anche come tipi di parametri, fino a quando si mantenere tutti i campi privati ​​(che è comunque una buona pratica). Here è una discussione a riguardo. Per rimuovere gli avvertimenti, è sufficiente utilizzare le istruzioni #pragma corrispondenti.

  • È possibile creare classi di wrapper di piccole dimensioni con campi STL privati ​​ed esporre i campi dei tipi di classe wrapper al pubblico, ma molto probabilmente spostare gli avvisi solo in altri punti.

  • È possibile utilizzare l'idioma per nascondere i campi STL in una classe di implementazione privata, che non verrà nemmeno esportata dalla libreria o visibile all'esterno di essa.

  • Oppure è possibile esportare tutte le specializzazioni di classe template richieste, come suggerito nell'avviso C4251, in un modo descritto come here. In breve si dovrebbe inserire seguenti righe di codice prima della classe HelloWorld:

    ... 
    #include <vector> 
    
    template class __declspec(dllexport) std::allocator<int>; 
    template class __declspec(dllexport) std::vector<int>; 
    template class __declspec(dllexport) std::string; 
    
    class __declspec(dllexport) HelloWorld 
    ... 
    

    Tra l'altro, l'ordine di queste esportazioni sembra essere importante: il modello di classe vettore utilizza il modello di classe allocatore internamente, in modo che il l'istanziazione allocatore deve essere esportata prima del l'istanza del vettore.

  • L'uso diretto della intrinseche è probabilmente la scelta peggiore, ma se si incapsulare i vostri campi

    int *p_abc; 
    int abc_len; 
    

    in qualcosa di simile class MyFancyDataArray ei campi

    char *p_str; 
    int str_len; 
    

    in qualcosa di simile class MyFancyString, allora questo sarebbe una soluzione più decente (simile a quella descritta al secondo punto). Ma probabilmente non è la migliore abitudine di reinventare la ruota troppo spesso.

+0

Ho provato il tuo 4 ° suggerimento, ma ottengo l'errore C2242 durante la compilazione con VS2010. – Simon

+0

È difficile dire, perché il compilatore si lamenta con C2242 senza vedere il codice o almeno il messaggio di errore completo, ma suppongo che probabilmente c'è un punto e virgola da qualche parte prima di un typedef. Forse la riga 'class __declspec (dllexport) HelloWorld' è stata copiata per caso? – buygrush

+2

@Simon: utilizzare questa classe di modello '__declspec (dllexport) std :: basic_string ;' –

5

o fare la cosa più facile da fare, spostare il __declspec ai soli membri si cura di esportazione:

class HelloWorld 
{ 
public: 
    __declspec(dllexport) HelloWorld() 
    { 
     abc.resize(5); 
     for(int i=0; i<5; i++) 
      abc[i] = i*10; 

     str="hello the world"; 
    } 
    __declspec(dllexport) ~HelloWorld() 
    { 
    } 
    std::vector<int> abc; 
    std::string str; 
}; 
+1

Esprote solo alcuni metodi ma non tutta la classe funziona davvero? – jpo38

+0

Lo fa, quando esporti la classe ciò che il compilatore fa è esportare tutti i membri della classe, non c'è davvero alcun simbolo di 'classe' nel file binario, ma c'è un (seppur squilibrato) HelloWorld :: HelloWorld() e HelloWorld: : ~ HelloWorld() in questo caso particolare. Dato che non si accede direttamente ai membri dati, non è necessario esportarli, tuttavia il compilatore deve ancora vedere la loro dichiarazione per poter calcolare la dimensione di un'istanza della classe. Rendere poi privato è sempre una buona pratica. –

+1

Quando si esportano singoli metodi da una DLL, è necessario fare attenzione a controllare l'accesso ai membri non esportati poiché il compilatore (per l'exe) genererà felicemente il codice appropriato per loro sull'altro lato della DLL e invocherà le funzioni esportate dalla DLL. Il controllo degli accessi deve essere esteso alle variabili membro e ai membri speciali (come copia e assegnazione); il migliore potrebbe essere quello di rendere i membri non esportati tutti privati ​​(o '= delete'). – Niall

3

io non sono sicuro di quale problema si vuole risolvere qui. Ci sono due problemi diversi: compatibilità binaria del compilatore incrociato ed evitare errori del linker "simbolo non definito". L'avviso C4251 che hai citato parla del secondo problema. Che è in gran parte per lo più un problema, specialmente con classi SCL come std::string e std::vector. Dal momento che sono implementati nel CRT, purché entrambi i lati dell'applicazione utilizzino lo stesso CRT, tutto "funzionerà". In tal caso, la soluzione è semplicemente disabilitare l'avviso.

Compatibilità binaria con il compilatore incrociato OTOH, che è ciò che viene discusso nell'altra domanda di stackoverflow collegata, è una bestia completamente diversa. Affinché ciò funzioni, in pratica devi mantenere tutti i tuoi file di intestazione pubblici privi di qualsiasi riferimento a qualsiasi classe SCL. Il che significa che devi PIMPL o usare classi astratte ovunque (o un mix di queste).

Problemi correlati