Je viens de réaliser quelque chose de dérangeant. Chaque fois que j'ai écrit une méthode qui accepte un std::string
comme paramètre, je me suis ouvert à un comportement indéfini.
Par exemple, cela ...
void myMethod(const std::string& s) {
/* Do something with s. */
}
... peut être appelé comme ça ...
char* s = 0;
myMethod(s);
... et je ne peux rien faire pour l'empêcher (à ma connaissance).
Ma question est donc: comment quelqu'un peut-il se défendre contre cela?
La seule approche qui me vient à l'esprit est de toujours écrire deux versions de toute méthode qui accepte un std::string
comme paramètre, comme ceci:
void myMethod(const std::string& s) {
/* Do something. */
}
void myMethod(char* s) {
if (s == 0) {
throw std::exception("Null passed.");
} else {
myMethod(string(s));
}
}
Est-ce une solution courante et / ou acceptable?
EDIT: Certains ont souligné que je devrais accepter const std::string& s
au lieu de std::string s
comme paramètre. Je suis d'accord. J'ai modifié le post. Je ne pense pas que cela change la réponse.
c_str
propriété de l'objet chaîne ?char* s = 0
défini. Je l'ai vu au moins quelques centaines de fois dans ma vie (généralement sous la forme dechar* s = NULL
). Avez-vous une référence pour appuyer cela?std:string::string(char*)
constructeurconst std::string&
pour ce paramètre ...?)Réponses:
Je ne pense pas que vous devriez vous protéger. C'est un comportement indéfini du côté de l'appelant. Ce n'est pas vous, c'est l'appelant qui appelle
std::string::string(nullptr)
, ce qui n'est pas autorisé. Le compilateur permet de le compiler, mais il permet également de compiler d'autres comportements non définis.La même manière serait d'obtenir une "référence nulle":
Celui qui déréférence le pointeur nul fait UB et en est responsable.
De plus, vous ne pouvez pas vous protéger après qu'un comportement indéfini s'est produit, car l'optimiseur a tout à fait le droit de supposer que le comportement indéfini ne s'est jamais produit, de sorte que la vérification si
c_str()
est nul peut être optimisée.la source
Le code ci-dessous donne un échec de compilation pour un passage explicite de
0
, et un échec d'exécution pour unchar*
avec une valeur0
.Notez que je n'implique pas que l'on devrait normalement faire cela, mais il ne fait aucun doute qu'il peut y avoir des cas où la protection contre l'erreur de l'appelant est justifiée.
la source
J'ai également rencontré ce problème il y a quelques années et je l'ai trouvé très effrayant. Cela peut arriver en passant un
nullptr
ou en passant accidentellement un int avec la valeur 0. C'est vraiment absurde:Cependant, à la fin, cela ne m'a dérangé que quelques fois. Et chaque fois, cela provoquait un crash immédiat lors du test de mon code. Aucune session nocturne ne sera donc nécessaire pour le réparer.
Je pense que surcharger la fonction avec
const char*
est une bonne idée.Je souhaite qu'une meilleure solution soit possible. Mais il n'y en a pas.
la source
foo(0)
et une erreur de compilation pourfoo(1)
Que diriez-vous de changer la signature de votre méthode pour:
De cette façon, l'appelant doit créer une chaîne avant de l'appeler, et la négligence qui vous inquiète causera des problèmes avant d'arriver à votre méthode, ce qui rend évident, ce que les autres ont souligné, que c'est l'erreur de l'appelant et non la vôtre.
la source
const string& s
, j'oubliais en fait. Mais même ainsi, ne suis-je pas encore vulnérable à un comportement indéfini? L'appelant peut toujours passer un0
, non?Que diriez-vous de fournir en surcharge le prend un
int
paramètre?Vous n'avez même pas besoin de définir la surcharge. Essayer d'appeler
myMethod(0)
déclenchera une erreur de l'éditeur de liens.la source
0
a unchar*
type.Votre méthode dans le premier bloc de code ne sera jamais appelée si vous essayez de l'appeler avec un
(char *)0
. C ++ essaiera simplement de créer une chaîne et lèvera l'exception pour vous. L'avez-vous essayé?Voir? Vous n'avez rien à craindre.
Maintenant, si vous voulez saisir cela un peu plus gracieusement, vous ne devriez tout simplement pas utiliser
char *
, le problème ne se posera pas.la source
std::string
va dans les bibliothèques utilisées par d'autres projets où je ne suis pas l'appelant. Je cherche un moyen de gérer la situation avec élégance et d'informer l'appelant (peut-être avec une exception) qu'il a passé un argument incorrect sans planter le programme. (OK, d'accord, l'appelant peut ne pas gérer une exception que je lance et le programme se bloquera de toute façon.)Si vous craignez que char * soit des pointeurs potentiellement nuls (par exemple, les retours des API C externes), la réponse est d'utiliser une version const char * de la fonction au lieu de la std :: string. c'est à dire
Bien sûr, vous aurez également besoin de la version std :: string si vous souhaitez autoriser leur utilisation.
En général, il est préférable d'isoler les appels aux API externes et de rassembler les arguments en valeurs valides.
la source