Initialiser std::string
à partir d'une NULL
caractère pointeur est un comportement indéfini, je crois. Donc, voici des versions alternatives d'un constructeur, où mStdString
est une variable membre de type std::string
:
void MyClass::MyClass(const char *cstr) :
mStdString( cstr ? cstr : "")
{}
void MyClass::MyClass(const char *cstr) :
mStdString(cstr ? std::string(cstr) : std::string())
{}
void MyClass::MyClass(const char *cstr)
{
if (cstr) mStdString = cstr;
// else keep default-constructed mStdString
}
Edit, déclaration du constructeur dans class MyClass
:
MyClass(const char *cstr = NULL);
Laquelle de ces méthodes, ou peut-être autre chose, est la meilleure ou la plus appropriée pour initialiser std::string
à partir d'un pointeur NULL
éventuel, et pourquoi? Est-ce différent pour différentes normes C++? Supposons que les indicateurs d’optimisation de la version finale soient disponibles.
Je cherche une réponse avec une explication de la raison pour laquelle une voie est la bonne, ou une réponse avec un lien de référence (cela s'applique également si la réponse est "n'a pas d'importance"), pas seulement des opinions personnelles doit, au moins en faire juste un commentaire).
Le dernier est stupide car il n’utilise pas l’initialisation quand il le pourrait.
Les deux premiers sont complètement identiques sémantiquement (pensez à la fonction membre c_str()
), préférez donc la première version car elle est la plus directe et la plus idiomatique, et la plus facile à lire.
(Il y a serait une différence sémantique si std::string
avait un constructeur par défaut constexpr
, mais ce n’est pas le cas. Pourtant, c’est possible que std::string()
est différent de std::string("")
, mais je ne sais pas. toutes les implémentations qui le font, car cela ne semble pas avoir beaucoup de sens. Par contre, les optimisations populaires à petite chaîne signifient de nos jours que les deux versions vont probablement - pas effectuer aucune allocation dynamique.)
Mise à jour: Comme @Jonathan le fait remarquer, les deux constructeurs de chaîne exécuteront probablement un code différent, et si cela vous importe (bien que cela ne devrait pas l'être), vous pourriez envisager une quatrième version:
: cstr ? cstr : std::string()
Lisible et construction par défaut.
Deuxième mise à jour: Mais préférez cstr ? cstr : ""
. Comme vous pouvez le voir ci-dessous, lorsque les deux branches appellent le constructeur same /, cela peut être implémenté de manière très efficace en utilisant des déplacements conditionnels et aucune branche. (Les deux versions génèrent donc un code différent, mais la première est meilleure).
Pour rire, j'ai exécuté les deux versions avec Clang 3.3, avec -O3
, sur x86_64, pour un struct foo;
comme le vôtre et une fonction foo bar(char const * p) { return p; }
:
Constructeur par défaut (std::string()
):
.cfi_offset r14, -16
mov R14, RSI
mov RBX, RDI
test R14, R14
je .LBB0_2
mov RDI, R14
call strlen
mov RDI, RBX
mov RSI, R14
mov RDX, RAX
call _ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm
jmp .LBB0_3
.LBB0_2:
xorps XMM0, XMM0
movups XMMWORD PTR [RBX], XMM0
mov QWORD PTR [RBX + 16], 0
.LBB0_3:
mov RAX, RBX
add RSP, 8
pop RBX
pop R14
ret
Constructeur de chaîne vide (""
):
.cfi_offset r14, -16
mov R14, RDI
mov EBX, .L.str
test RSI, RSI
cmovne RBX, RSI
mov RDI, RBX
call strlen
mov RDI, R14
mov RSI, RBX
mov RDX, RAX
call _ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm
mov RAX, R14
add RSP, 8
pop RBX
pop R14
ret
.L.str:
.zero 1
.size .L.str, 1
Dans mon cas, il semblerait même que ""
génère better code: les deux versions appellent strlen
, mais la version chaîne vide n'utilise aucun saut, mais uniquement des déplacements conditionnels (étant donné que le même constructeur est appelé, avec deux arguments différents). Bien sûr, il s’agit d’une observation totalement dépourvue de sens, non portable et non transférable, mais cela montre simplement que le compilateur n’a pas toujours besoin de autant d’aide que vous pourriez le penser. Écrivez simplement le code qui vous convient le mieux.
Tout d’abord, vous avez raison, à partir de http://www.cplusplus.com/reference/string/string/string/ :
Si s est un pointeur nul, si n == npos ou si la plage spécifiée par [premier, dernier) n'est pas valide, cela entraîne un comportement indéfini.
En outre, cela dépend de ce qu’un pointeur NULL signifie pour vous. Je suppose que c'est la même chose qu'une chaîne vide pour vous.
J'irais avec le premier, parce que c'est celui que je lis le mieux. La première solution et la seconde sont les mêmes. La troisième ne fonctionnerait pas si votre chaîne était const
.
En supposant que vous soyez satisfait du cstr == NULL
et que vous donniez un mStdString
vide, je pense que le premier est probablement le meilleur.
Si rien d'autre, la troisième option que vous fournissez ne fonctionne pas si mStdString
est const
. L'option médiane bénéficie de la "sémantique de déplacement" sous C++ 11, mais est moins évidente qu'optimale ou raisonnable.
Donc, mon vote va avec la première option.
Bien que cela puisse ne pas être VRAIMENT une réponse (en particulier lorsque vous avez formulé la question) - mais c'est trop long pour tenir lieu de commentaire et contenir du code qui ne contient pas de commentaires. Je m'attends vraiment à avoir un vote négatif et à supprimer ce message - mais je me sens obligé de dire quelque chose.
POURQUOI le char *
d'initialisation serait-il NULL - et si c'est le cas, ne pourriez-vous pas le transmettre à l'appelant pour lui donner une idée de la situation - par exemple, en transmettant une chaîne vide, ou "unknown"
ou "(null)"
selon le cas.
En d'autres termes, quelque chose comme ceci:
void MyClass::MyClass(const char *cstr)
{
assert(cstr != NULL); // or "throw cstr_must_not_be_null;" or some such.
mStdString = cstr;
}
(Il existe probablement un moyen astucieux de faire cela dans une liste d’initialisation, mais je ne me soucie pas de savoir comment le faire correctement).
Pour ma part, je n'apprécie pas NULL en tant qu'entrée dans un paramètre de chaîne d'une autre manière que "ceci n'existe pas vraiment" - et si c'est ce que vous essayez réellement de répliquer, vous devriez alors avoir une boolean
pour dire "ne 'existe pas ", ou un pointeur sur un std::string
qui peut être NULL si aucune chaîne n'est présente.