Programmazione in C++: stranezze con la classe string

Oggi un lettore (medeoTL, grazie!) mi ha fatto notare che nel programmino che avevo proposto ieri ho scritto una cosa inesatta. Ho scritto che l’istruzione v2[2] = 5; non compilava. E invece compila e funziona correttamente. Per capire di cosa stiamo parlando, ecco un programmino che mette in risalto il problema.

#include <string>
#include <iostream>

using namespace std;

int main()
{
    string nome = "Ok";
    string cognome;
    cognome = "Panico";
    string s;
    s = 65;
    cout << s << endl;
    string s1(65);
    cout << s1 << endl;
}

In questo programmino mi limito ad usare la classe string della libreria standard.
La prima istruzione crea un oggetto di tipo string e gli assegna come valore iniziale la stringa “Ok”.
La seconda istruzione invece crea l’oggetto cognome, che è una stringa vuota, e la terza istruzione assegna il valore “Panico”.

Sembra la stessa cosa, e invece sono due operazioni diverse. Nel caso dell’oggetto nome, sto creando l’oggetto con un valore iniziale. La funzione che si occupa di creare un oggetto e assegnare il valore iniziale si chiama “costruttore”. Il suo prototipo è qualcosa simile a questo:

class string {
    ...
    string(const char *s);
    ...
};

Si tratta di una funzione che ha lo stesso nome della classe, e prende in input un puntatore a carattere.

Quando viene costruito l’oggetto cognome, invece, non si da alcun valore iniziale. Si usa allora un altro costruttore che non prende alcun parametro (detto “costruttore di default”). Più o meno una cosa così:

class string {
    ...
    string();
    string(const char *s);
    ...
};

Avete quindi visto due costruttori, uno di default e uno che prende un puntatore a carattere.

Infine, assegnamo un valore all’oggetto cognome: in questo caso, viene invocata un’altra funzione che si chiama “operatore di assegnamento”, che ha questo proprotipo:

class string {
    ...
    string();
    string(const char *s);
    ...
    string &operator=(const char *s);
};

Come vedete, dunque, ci sono ben due modi di assegnare valori a una stringa. Durante la creazione, viene chiamato il costruttore, mentre se l’assegnamento avviene dopo la creazione, viene chiamato l’operatore di assegnamento.

Potete anche notare una particolarità del C++ (e non solo): ci possono essere più funzioni con lo stesso nome, ma con parameteri diversi. Per il C++ sono a tutti gli effetti funzioni completamente diverse. Come fa il C++ a sapere quale chiamare? beh, a seconda del numero e tipo degli argomenti. Ricordate? Il C++ applica lo strong typing, e quindi è possibile capire quale funzione chiamare a seconda del tipo del parametro.

E veniamo al punto. In realtà di operatori assegnamento ce ne sono 3 versioni:

class string {
    ...
    string();
    string(const char *s);
    ...
    string& operator= ( const string& str );
    string& operator= ( const char* s );
    string& operator= ( char c );
};

Il primo viene chiamato quando a destra dell’uguale c’è un’altro oggetto di tipo stringa; il secondo quando c’è un puntatore a carattere (ovvero una “stringa” del vecchio C), il terzo quando c’è un semplice carattere.

E ora torniamo al nostro programma di prova iniziale, e osserviamo le istruzioni seguenti:

    ...
    string s;
    s = 65;
    cout << s << endl;
    string s1(65);
    cout << s1 << endl;
    ...

L’istruzione s = 65 non da alcun problema, perché una costante può essere convertita implicitamente in un char, e quindi viene chiamata la terza versione dell’operatore assegnamento! Se eseguite il codice, vi stamperà una bella A maiuscola (corrispondente al codice ASCII 65).

L’istruzione string s1(65); invece da un errore di compilazione, esattamente questo:

lipari@lipari$ g++ prova.cpp 
prova.cpp: In function ‘int main()’:
prova.cpp:12:17: error: invalid conversion from ‘int’ to ‘const char*’ [-fpermissive]
/usr/include/c++/4.6/bits/basic_string.tcc:214:5: error:   initializing argument 1 of ‘std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char, _Traits = std::char_traits<char>, _Alloc = std::allocator<char>]’ [-fpermissive]

Cioè, non esiste un costruttore di stringa che prenda in ingresso un carattere e basta; il compilatore cerca disperatamente di convertire l’intero 65 in un puntatore a char, ma questa conversione è vietata (a meno di usare il flag di compilazione -fpermissive), e quindi da errore.

A mio modesto parere, questa cosa qui è una cavolata. Secondo la logica e secondo tutti i guru e i libri di testo, il costruttore e l’operatore di assegnamento dovrebbero essere “simmetrici”, proprio per evitare strani comportamenti come quello descritto nell’esempio. Quindi, o tutti e due prendono per buono il carattere isolato; oppure nessuno dei due. Per la classe string, invece, questa regola d’oro è stata violata, perché?

Proabbilmente perché la classe è stata una delle prime ad essere progettata e realizzata nella libreria, quando ancora non era chiarissimo cosa era giusto e cosa non lo era; e poi cambiarla è diventato impossibile, o quantomeno molto difficile per problemi di compatibilità del codice.

Insomma, nessuno è perfetto (tantomeno la libreria standard c++, anche se ci si avvicina abbastanza!)

About these ads
Posta un commento o usa questo indirizzo per il trackback.

Lascia un Commento

Fill in your details below or click an icon to log in:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Connessione a %s...

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Unisciti agli altri 37 follower

%d bloggers like this: