Après C++ 11, j'ai pensé à c_str()
et data()
de manière équivalente .
C++ 17 introduit une surcharge pour ce dernier, qui retourne un pointeur non constant ( reference , que je ne sais pas s'il est complètement mis à jour avec w.r.t. C++ 17):
const CharT* data() const; (1)
CharT* data(); (2) (since C++17)
c_str()
ne renvoie qu'un pointeur constant:
const CharT* c_str() const;
Pourquoi la différenciation de ces deux méthodes en C++ 17, en particulier lorsque C++ 11 est celui qui les a rendues homogènes? En d'autres termes, pourquoi une seule méthode a-t-elle été surchargée, alors que l'autre n'en a pas?
La nouvelle surcharge a été ajoutée par P0272R1 pour C++ 17. Ni le document lui-même, ni les liens qu’il contient n’expliquent pourquoi seul data
a reçu de nouvelles surcharges mais c_str
n'était pas. Nous pouvons seulement spéculer à ce stade (à moins que des personnes impliquées dans la discussion ne se mêlent), mais j'aimerais proposer les points suivants à l'examen:
Même juste en ajoutant la surcharge à data
cassé du code; Garder ce changement conservateur était un moyen de minimiser l'impact négatif.
Le c_str
fonction était jusqu’à présent entièrement identique à data
et est en réalité une fonctionnalité "héritée" pour interfacer du code prenant "chaîne C", c’est-à-dire un immuable , tableau de caractères à zéro terminal. Comme vous pouvez toujours remplacer c_str
by data
, il n’ya aucune raison particulière d’ajouter à cette interface héritée.
Je me rends compte que la motivation même de P0292R1 était qu'il existe des API héritées qui, par erreur ou pour des raisons C, ne prennent que des pointeurs mutables, même si elles ne mutent pas. Quoi qu'il en soit, je suppose que nous ne voulons pas ajouter davantage à l'API déjà massive de string qui est absolument nécessaire.
Un dernier point: à partir de C++ 17, vous êtes maintenant autorisé à écrire jusqu'au terminateur nul, tant que vous écrivez la valeur zéro. (Auparavant, c'était UB pour écrire quoi que ce soit sur le terminateur nul). Un mutable c_str
créerait encore un autre point d’entrée dans cette subtilité particulière, et moins nous aurions de subtilités, mieux ce serait.
La raison pour laquelle le membre data()
a été surchargé est expliquée dans this paper à open-std.org.
TL; DR du papier : La fonction non-const .data()
pour _std::string
_ a été ajoutée pour améliorer l'uniformité dans la bibliothèque standard et d’aider les développeurs C++ à écrire du code correct. C'est également pratique lorsque vous appelez une fonction de bibliothèque C qui ne possède pas de qualification const sur ses paramètres de chaîne.
Quelques passages pertinents du papier:
Résumé
L'absence d'une fonction membre non-const.data()
membre de _std::string
_ est-elle un oubli ou une conception intentionnelle basée sur une sémantique antérieure à C++ 11 _std::string
_? Dans les deux cas, ce manque de fonctionnalités pousse les développeurs à utiliser des alternatives non sécurisées dans plusieurs scénarios légitimes. Cet article plaide en faveur de l'ajout d'une fonction membre non-const.data()
pour std :: string afin d'améliorer l'uniformité de la bibliothèque standard et d'aider les développeurs C++ à écrire du code correct.Cas d'utilisation
Les bibliothèques C incluent parfois des routines ayant des paramètres char *. Un exemple est le paramètrelpCommandLine
de la fonctionCreateProcess
dans l'API Windows. Étant donné que le membredata()
de _std::string
_ est const, il ne peut pas être utilisé pour que les objets std :: string fonctionnent avec le paramètrelpCommandLine
. Les développeurs sont tentés d'utiliser plutôt.front()
, comme dans l'exemple suivant._std::string programName; // ... if( CreateProcess( NULL, &programName.front(), /* etc. */ ) ) { // etc. } else { // handle error }
_Notez que lorsque
programName
est vide, l'expressionprogramName.front()
provoque un comportement indéfini. Une chaîne de caractères vide temporaire corrige le bogue._std::string programName; // ... if( !programName.empty() ) { char emptyString[] = {'\0'}; if( CreateProcess( NULL, programName.empty() ? emptyString : &programName.front(), /* etc. */ ) ) { // etc. } else { // handle error } }
_S'il y avait un membre non-const
.data()
, comme avec _std::vector
_, le code correct serait simple._std::string programName; // ... if( !programName.empty() ) { char emptyString[] = {'\0'}; if( CreateProcess( NULL, programName.data(), /* etc. */ ) ) { // etc. } else { // handle error } }
_Une fonction membre non-const
.data() std::string
est également pratique pour appeler une fonction de bibliothèque C qui ne possède pas la qualification const sur ses paramètres C-string. Ceci est courant dans les anciens codes et ceux qui doivent être portables avec les anciens compilateurs C.
Cela dépend de la sémantique de "ce que vous voulez en faire". De manière générale, std::string
Est parfois utilisé comme vecteur de tampon, c'est-à-dire en remplacement de std::vector<char>
. Cela se voit souvent dans boost::asio
. En d'autres termes, c'est un tableau de caractères.
c_str()
: signifie strictement que vous recherchez une chaîne terminée par un zéro. En ce sens, vous ne devriez jamais modifier les données et vous ne devriez jamais avoir besoin de la chaîne en tant que non-const.
data()
: vous aurez peut-être besoin des informations contenues dans la chaîne sous forme de données tampons, et même non const. Vous pouvez ou non avoir besoin de modifier les données, ce que vous pouvez faire, pour autant que cela n'implique pas de changer la longueur de la chaîne.
Les deux fonctions membres c_str et data de std :: string existent en raison de l'historique de la classe std :: string.
Jusqu'en C++ 11, un std :: string aurait pu être implémenté en tant que copie sur écriture. La représentation interne ne nécessitait aucune terminaison null de la chaîne stockée. La fonction membre c_str s’assurait que la chaîne renvoyée était terminée par zéro. La fonction membre data simlpy a renvoyé un pointeur sur la chaîne stockée, qui n'était pas nécessairement terminée par zéro. - Pour être sûr que les modifications apportées à la chaîne étaient remarquées pour permettre la copie sur écriture, les deux fonctions devaient renvoyer un pointeur sur les données const.
Tout cela a changé avec C++ 11 lorsque la copie en écriture n'était plus autorisée pour std :: string. Comme c_str était toujours requis pour livrer une chaîne terminée par un caractère null, le caractère null est toujours ajouté à la chaîne stockée. Sinon, un appel à c_str devra peut-être modifier les données stockées pour que la chaîne soit terminée par une chaîne, ce qui ferait de c_str une fonction non-const. Puisque data fournit un pointeur sur la chaîne stockée, elle a généralement la même implémentation que c_str. Les deux fonctions existent toujours en raison de la compatibilité ascendante.