Odpowiedź brzmi: to zależy od tego, z jakim standardem C ++ kompilujesz. Cały kod jest doskonale sformułowany we wszystkich standardach ‡ z wyjątkiem tego wiersza:
char * s = "My String";
Teraz literał ciągu ma typ const char[10]i próbujemy zainicjować do niego wskaźnik niebędący stałym. Dla wszystkich typów innych niż charrodzina literałów łańcuchowych taka inicjalizacja była zawsze niedozwolona. Na przykład:
const int arr[] = {1};
int *p = arr; // nope!
Jednak w pre-C ++ 11, dla literałów ciągów, był wyjątek w §4.2 / 2:
Literał łańcuchowy (2.13.4), który nie jest literałem szerokiego łańcucha, można przekonwertować na wartość r typu „ wskaźnik na znak ”; […]. W obu przypadkach wynikiem jest wskaźnik do pierwszego elementu tablicy. Ta konwersja jest brana pod uwagę tylko wtedy, gdy istnieje wyraźny odpowiedni typ docelowy wskaźnika, a nie wtedy, gdy istnieje ogólna potrzeba konwersji z l-wartości na r-wartość. [Uwaga: ta konwersja jest przestarzała . Patrz załącznik D. ]
Tak więc w C ++ 03 kod jest całkowicie w porządku (chociaż jest przestarzały) i ma jasne, przewidywalne zachowanie.
W C ++ 11 ten blok nie istnieje - nie ma takiego wyjątku dla literałów ciągów konwertowanych na char*, więc kod jest tak samo źle sformułowany, jakint* przykład, który właśnie podałem. Kompilator jest zobowiązany do wydania diagnostyki, a najlepiej w przypadkach takich jak ten, które są wyraźnym naruszeniem systemu typu C ++, oczekiwalibyśmy, że dobry kompilator nie tylko będzie zgodny w tym zakresie (np. Wydając ostrzeżenie), ale zawiedzie wprost.
Idealnie byłoby, gdyby kod nie był kompilowany - ale działa zarówno na gcc, jak i clang (zakładam, że prawdopodobnie istnieje wiele kodu, który zostałby zepsuty z niewielkim zyskiem, pomimo tego, że ten typ dziury w systemie jest przestarzały przez ponad dekadę). Kod jest źle sformułowany, dlatego nie ma sensu rozważać, jakie może być zachowanie kodu. Biorąc jednak pod uwagę ten konkretny przypadek i historię wcześniejszego dopuszczania go, nie uważam, aby interpretowanie powstałego kodu tak, jakby był to dorozumiany const_cast, był nierozsądnym naciąganiem , na przykład:
const int arr[] = {1};
int *p = const_cast<int*>(arr); // OK, technically
Dzięki temu reszta programu jest w porządku, ponieważ nigdy więcej nie dotykasz s. Czytanie utworzonego constobiektu za pomocą constwskaźnika niebędącego wskaźnikiem jest całkowicie OK. Pisanie utworzonego constobiektu za pomocą takiego wskaźnika jest niezdefiniowanym zachowaniem:
std::cout << *p; // fine, prints 1
*p = 5; // will compile, but undefined behavior, which
// certainly qualifies as "unpredictable"
Ponieważ sw kodzie nie ma żadnych modyfikacji , program działa dobrze w C ++ 03, nie powinien się skompilować w C ++ 11, ale i tak robi - a biorąc pod uwagę, że kompilatory na to pozwalają, nadal nie ma w nim niezdefiniowanego zachowania † . Biorąc pod uwagę fakt, że kompilatory nadal [niepoprawnie] interpretują reguły C ++ 03, nie widzę niczego, co prowadziłoby do „nieprzewidywalnego” zachowania. Napisz sjednak, a wszystkie zakłady są wyłączone. Zarówno w C ++ 03, jak i C ++ 11.
† Chociaż, ponownie, z definicji źle sformułowany kod nie daje żadnych oczekiwań dotyczących rozsądnego zachowania
‡ Z wyjątkiem, że nie, zobacz odpowiedź Matta McNabba