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!)
