web-dev-qa-db-fra.com

Initialise std :: string à partir d'un pointeur de caractère éventuellement NULL

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

19
hyde

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.

19
Kerrek SB

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.

2
Xaqq

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.

1
Joe Z

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. 

0
Mats Petersson