2015-12-30 3 views
2

J'ai la fonction suivante qui effectue initialement une validation sur les paramètres de la fonction.C++ const char * à char *

char *doSomething(const char* first, const char* second) { 

    if((first == nullptr || *first == '\0') && (second == nullptr || *second == '\0')) { 
     return nullptr; 
    } else if (first == nullptr || *first == '\0') { 
     return (char *) second; 
    } else if (second == nullptr || *second == '\0') { 
     return (char *) first; 
    } 
    //doSomething 
} 

Est-ce que la coulée du paramètre de fonction retourne une nouvelle char* qui pointe vers une autre zone en mémoire? Je ne veux pas permettre à quelqu'un utilisant cette fonction de manipuler la valeur que les paramètres constants pointent vers. Je voudrais qu'un nouveau char* soit retourné avec la même valeur que l'un des paramètres si l'un est nullptr ou vide.

Suivi: Une variable booléenne serait-elle meilleure ici? Je réalise que j'effectue la même vérification pour chaque variable deux fois, mais je n'utiliserais pas ce booléen ailleurs dans le code de cette fonction.

+1

Et puis, j'appelle 'doSomething (" bonjour "," monde! ") [0] = 'X';' et boom! SIGSEGV. (la réponse est non, vous devrez 'strncpy()' ces chaînes – YSC

+0

Si vous ne voulez pas permettre à quelqu'un utilisant cette fonction de manipuler la valeur que les paramètres constants pointent retourner par 'const char *' ou return _by value_.La fonction effectue une conversion implicite 'const_cast' – Ziezi

+0

Voulez-vous dire que vous voulez retourner une copie mutable de la chaîne? Sinon, pourquoi ne pas simplement retourner un' const char * '? – rici

Répondre

5

Non, il ne fait aucun nouvel objet, il renverse const à la zone de mémoire que vous avez déclaré être immuable. Qui normalement résultats dans le comportement indéfini redouté, mais si elles proviennent de pointeur non const vous serez ok (EDIT - grâce à @anatolyg).

+0

@anatolyg Merci –

+0

Je dirais que non seulement * normalement * mais * toujours * le 'const_cast' est très bien, il ne résulte jamais en UB. * Écrire sur l'objet * (ne pas lire!) à travers le pointeur résultant peut entraîner dans certaines circonstances UB.Un d'entre eux (le seul?) est que l'objet est en mémoire morte, comme une chaîne de caractères littérale dans les implémentations modernes.Mais c'est plutôt rare, le cas le plus commun est-ce un pa rameter a été déclaré const pour indiquer l'intention de la fonction de ne pas le changer, mais cette fonction en appelle une autre qui n'a pas déclaré son param const de sorte qu'elle doit être castée. –

+0

@ PeterA.Schneider Bien sûr, mais dans le cas général, vous ne pouvez pas dire si oui ou non non-'const' sera écrit ou non. Et UB est UB indépendamment de la mémoire en lecture seule ou autrement. –

2

Aucune donnée n'est copiée automatiquement. Vous devez également renvoyer un const char*. Le comportement sur la suppression du const et la tentative de modification de la chaîne de caractères d'origine via ce pointeur est indéfini si les données d'origine étaient à l'origine const: tel qu'un littéral de chaîne. Effectuez une copie en profondeur en dehors de la fonction si vous avez besoin d'un pointeur char*

Mieux encore, ben tout cela et passer std::string soit par const pointeur si nullptr est encore autorisée ou par const référence si ce n'est pas.

+0

Pour être tapoté ici aussi: Le comportement sur l'élimination de const est * jamais * indéfini. Cela entraîne toujours un pointeur valide vers l'objet d'origine. "Valide" signifie que, par exemple, cette valeur d'adresse ne sera pas piégée si elle est placée dans un registre. Comme il pointe vers l'objet original, cet objet peut toujours être lu sans UB. La seule chose qui est UB est * écriture * à un tel objet s'il est en effet en lecture seule (comme un littéral de chaîne dans les implémentations modernes). Lire à partir d'un tel pointeur à travers un pointeur non-const est parfaitement bien cependant. –

+0

@ PeterA.Schneider: Très vrai, j'ai modifié. – Bathsheba

0

Vous êtes peut-être en train de renvoyer un pointeur vers le premier ou le deuxième param afin que l'appelant de votre fonction puisse modifier les données vers lesquelles il pointe. Vous renvoyez techniquement un nouveau pointeur (copie de celui-ci) mais à cause de la nature du pointeur, l'appelant peut modifier les données avec lui.

Si vous ne pouvez pas renvoyer const char *, l'appelant ne peut pas le modifier ou ne pas le retourner du tout.

Une variable booléenne peut rendre votre code plus clair et éviter la duplication de code.

0

La création d'une valeur ne le copiera pas. Si vous voulez le copier vous devrez passer par la valeur, passant par le pointeur ou la référence permettra à l'utilisateur de manipuler la valeur retournée (à moins que ce soit const).

Je ne vois pas pourquoi vous vous inquiétez que l'utilisateur manipule les paramètres car ils sont effectivement constants.

Et pour les vérifications nuls, l'utilisation d'un bool est inutile. Les vérifications nulles sont peu coûteuses à utiliser (performance sage).

2

La norme 2011 dit (5.2.11/3):

Le résultat d'un const_cast pointeur fait référence à l'objet d'origine.

C'est assez simple et répond à votre question initiale.

Il dit aussi (5.2.11/7):

[Note: En fonction du type de l'objet, une opération d'écriture par le pointeur, lvalue ou pointeur sur les données résultant d'un membre const_cast qui rejette un qualificatif const peut produire un comportement non défini (7.1.6.1). -end note]

Cela signifie qu'il est parfois correct d'écrire même dans l'objet via le nouveau pointeur. Les exemples où ce n'est pas correct sont des pointeurs vers des objets qui résident dans la mémoire en lecture seule. Cela a mordu les gens en utilisant des pointeurs vers des littéraux de chaîne, et se produit fréquemment avec des constantes dans les systèmes embarqués. Le casting lui-même n'est, pour autant que je puisse le voir, jamais un comportement indéfini.

Concernant votre question d'examen du code:

  • Comme les autres ont dit, par tous les moyens de rappel un const char.
  • L'appelant fait-il quelque chose avec le résultat? Si ce n'est pas le cas, renvoyez un booléen indiquant le succès ou l'échec, ou un int pour plusieurs modes de défaillance.
  • Si l'appelant utilise le pointeur char retourné, que renvoyez-vous pour indiquer le succès? Les trois cas d'erreur possibles utilisent les valeurs de retour immédiatement disponibles. Pensez à renvoyer un code d'erreur et à passer le résultat du pointeur char actuel à un "paramètre out" (un pointeur vers un pointeur char).
0

J'utiliser un indicateur booléen pour éviter la duplication de code et de le rendre moins sensible:

bool secondIsEmpty = second == nullptr || *second == '\0'; 
if(first == nullptr || *first == '\0') 
    return secondIsEmpty ? nullptr : second; 
else 
    if(secondIsEmpty) 
     return first; 

Pour char *, vous devez modifier le type de retour à const char * et enlever fonte C à partir de votre code (vous devez utiliser const_cast <> à la place de toute façon). Si vous avez besoin de renvoyer une chaîne mutable à partir de cette fonction, cela complique la tâche. Vous devez soit accepter des chaînes mutables en tant que paramètres, soit créer un tampon quelque part. Dans ce cas, vous devriez retourner le pointeur intelligent à la chaîne, attribué par new ou encore mieux utiliser std::string et le renvoyer par valeur.

0

(Cette réponse est essentiellement d'un côté la question sur la façon de rendre le code plus)

Comme les gens ici ont noté, vous voulez probablement revenir const char*:

const char *doSomething(const char* first, const char* second) 
{ 
    ... 
} 

Cela vous permettra d'obtenir débarrasser des modèles:

const char *doSomething(const char* first, const char* second) 
{ 
    if (...) 
    { 
     return nullptr; 
    } 
    else if (...) 
    { 
     return second; 
    } 
    else if (...) 
    { 
     return first; 
    } 
} 

idée de votre code est à peu près:

Recherchez et renvoyez la chaîne non vide, avec préférence à second one; s'il n'y en a pas, retournez nullptr.

Il peut être plus facilement représentés comme ceci:

const char *FindNonEmpty(const char* first, const char* second) 
{ 
    if (second && *second) 
     return second; 
    else if (first && *first) 
     return first; 
    else 
     return nullptr; 
} 

je pointeurs et octets ici dans le contexte booléen.C'est une question de goût; vous voulez probablement utiliser des comparaisons explicites; Cependant, certaines personnes soutiennent que l'utilisation d'un pointeur comme un booléen est plus lisible que de le comparer à nullptr.