2010-09-15 7 views
3

Je me demande si ce bit de code présente le bon comportement C++?Comportement correct de l'opérateur conditionnel dans VS2010?

class Foo 
{ 
public: 
    Foo(std::string name) : m_name(name) {} 

    Foo(const Foo& other) { 
     std::cout << "in copy constructor:" << other.GetName() << std::endl; 
     m_name = other.GetName(); 
    } 

    std::string GetName() const { return m_name; } 
    void SetName(std::string name) { m_name = name; } 

private: 
    std::string m_name; 
}; 

Foo CreateFoo(std::string name) 
{ 
    Foo result(name); 
    return result; 
} 

void ChangeName(Foo& foo) 
{ 
    foo.SetName("foofoo"); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Foo fooA("alan"); 
    std::cout << "fooA name: " << fooA.GetName() << std::endl; 
    bool b = true; 
    ChangeName(b ? fooA : CreateFoo("fooB")); 
    std::cout << "fooA name: " << fooA.GetName() << std::endl; 
    return 0; 
} 

Une fois construit en VS2008 la sortie est:

fooA name: alan 
fooA name: foofoo 

Mais quand le même code est construit en VS2010 il devient:

fooA name: alan 
in copy constructor: alan 
fooA name: alan 

Un constructeur de copie est invoquée sur « alan 'et, bien qu'il soit passé par référence (ou non selon le cas), fooA est inchangé par l'appelé à ChangeName. La norme C++ a-t-elle changé, a-t-elle corrigé un comportement incorrect ou a-t-elle introduit un bogue? Par ailleurs, pourquoi le constructeur de copie est-il appelé?

+4

Votre code me semble mal formé, donc je ne m'attends pas à ce qu'il y ait une réponse à vos questions à ce sujet. Vous attendez une référence en tant que param à ChangeName mais lui donner une expression qui crée un temporaire. –

+1

Oui. Amenez le niveau d'avertissement à 4 et compilez à nouveau. –

+0

@Noah: Ou désactiver les extensions de langue dans le compilateur VC, qui émettra une erreur de compilation qui dit exactement quel est le problème: 'error C2664: 'ChangeName': ne peut pas convertir le paramètre 1 de 'Foo' à 'Foo &'' –

Répondre

5

Une réponse plus complète:

5,16/4 & 5:.

« 4 Si les deuxième et troisième opérandes sont lvalues ​​et ont le même type, le résultat est de ce type et est une lvalue

5 Sinon, le résultat est un rvalue .... "

En d'autres termes," bool? Lvalue: rvalue "donne un résultat temporaire. Ce serait la fin de celui-ci, mais vous passez cela dans une fonction qui, selon C++, DOIT recevoir un lvalue comme paramètre. Puisque vous lui passez une valeur, vous avez du code qui n'est pas C++. MSVC++ l'accepte parce qu'il est stupide et utilise un tas d'extensions dont il ne vous parle pas à moins que vous ne le transformiez en pendentif. Puisque ce que vous avez n'est pas standard en C++ pour commencer, et que MS le permet par extension, rien ne peut vraiment être dit sur ce qui est "correct" à ce sujet.

+0

Visual Studio 2010 autorise la méthode standard de liaison à RVAlue:'void ChangeName (Foo && foo) '. Cela donne le même comportement que le code OPs. Si cela est conforme à la norme si je n'ai pas compris. – FuleSnabel

-1

Le compilateur évalue clairement les deux côtés de:.

+0

Non, ça ne l'est pas. Cependant, pour faire une résolution de type du résultat de l'expression, le * type de * deux côtés doit être évalué. –

+0

Le compilateur n'évalue PAS les deux côtés lors de l'exécution. –

3

Dans votre expression conditionnelle, votre deuxième opérande est un lvalue de type Foo, tandis que le troisième est un rvalue de type Foo (valeur de retour d'une fonction ne retourne pas une référence).

Cela signifie que le résultat du conditionnel est un rvalue pas lvalue (quelle que soit la valeur de la première expression), que vous ne pouvez pas lier ensuite à une référence non-const. Comme vous avez violé cette règle, vous ne pouvez pas invoquer la norme de langage pour indiquer le comportement correct de l'une ou l'autre version du compilateur.

Le résultat d'un conditionnel est une lvalue si les deux deuxième et troisième opérandes sont lvalues ​​ du même type.

Édition: Techniquement, les deux versions ne respectent pas la norme car elles n'ont pas émis de diagnostic lorsque vous avez enfreint une règle pouvant être diagnostiquée de la norme.

+0

Merci Charles. Noah vous a battu au coup de poing, mais cette réponse est sur place. – WalderFrey

0

Pour autant que je sache, cela est couvert par la norme C++ en 5.16 point 3, n'est-ce pas? Il dit "si E2 est une valeur, ou si la conversion ci-dessus ne peut être faite: si E1 et E2 ont un type de classe, et les types de classe sous-jacents sont identiques ou l'un est une classe de base de l'autre: E1 peut être converti pour correspondre à E2 si la classe de T2 est du même type que, ou une classe de base de la classe de T1, et la qualification cv de T2 est la même qualification cv que, ou une plus grande qualification cv que, la cv-qualification de T1 Si la conversion est appliquée, E1 est changé en une valeur de type T2 qui fait toujours référence à l'objet de classe source d'origine (ou à son sous-objet approprié) [Note: c'est-à-dire qu'aucune copie n'est faite.] "

Cela ne décrit-il pas la situation ci-dessus? Je pensais que c'était le cas, mais je suis prêt à accepter que j'ai peut-être tort si quelqu'un pouvait expliquer pourquoi cela ne fonctionne pas.

Merci.

+0

Ce paragraphe entier ne s'applique que si les types des deuxième et troisième opérandes sont différents ("Sinon, si les deuxième et troisième opérandes ont des types différents ..."). Ici les types sont les mêmes, ils sont tous les deux non-const, non volatiles 'Foo' donc le paragraphe n'est pas applicable. –

+0

Merci Charles - il me semble un peu étrange que si les types sont identiques (sauf pour le problème rvalue/lvalue) nous devrions nous retrouver avec du C++ non-standard, mais si les types sont différents, alors ça va (et les références sont implicites) - au moins si je comprends bien cela. Merci pour votre clarification de toute façon. –

+0

Il n'y a aucun problème à avoir une _lvalue_ et une _rvalue_ dans une expression conditionnelle, cela signifie simplement que l'expression est un _rvalue_. L'erreur dans le code d'origine est la tentative de lier cette _rvalue_ à une référence non-const; c'est la violation de la norme dans ce cas. –

0

Bizarrement, en référence au commentaire "Oui, retournez le niveau d'avertissement à 4 et recompilez". (Noah Roberts), apparemment il y a un avertissement à condition que 'ChangeName' prenne une référence non-const. Si c'est une fonction qui prend une référence constante, il n'y a pas d'avertissement, mais la variable temporaire est toujours créée. Peut-être que c'est juste un autre vague du compilateur de Microsoft.