2009-09-04 2 views
4

Un appel de constructeur peut-il être évalué en un booléen si l'opérateur bool() est surchargé?Pouvez-vous évaluer un appel de constructeur à boolean avec un bool() surchargé?

class A 
{ 
    public: 
    A() {}; 
    operator bool() const { return true; } 
} 

main() 
{ 
    if (A a = A()) 
    { 
    // do stuff 
    } 
} 

est le code ci-dessus valide, ou dois-je mettre en œuvre principale comme:

int main(int argc, const char* argv[]) 
{ 
A a(); 
if (a) 
    { 
    // do stuff 
    } 
} 

Ce code va liquider partout dans ma base de code, les lignes donc moins, a augmenté la lisibilité, et la portée réduite sont importantes, et seraient améliorées par ceci.

Des idées?

+1

Quelle est votre intention avec ce code? Est-ce que la conversion à bool retourne toujours vrai? Si c'est le cas, l'instruction if est redondante. –

+2

Je suppose que c'est un morceau de code rapide. "Bool operator()() {return foo;}" ne serait-il pas un meilleur exemple? –

+0

A quoi sert-elle? Cherchez-vous des erreurs? – GManNickG

Répondre

8

Le code contient quelques bogues syntaxiques et sémantiques. Corrigeons-les

class A 
{ 
public: 
    A() {}; 
    operator bool() { return true; } 
}; 

int main() 
{ 
    if (A a = A()) 
    { 
    // do stuff 
    } 
} 

Vous pouvez choisir de changer le type de la fonction de conversion en autre chose. Comme écrit, la conversion booléenne réussira également à se convertir en n'importe quel type entier. La conversion en void* limitera la conversion à bool et void*, qui est un idiome couramment utilisé. Encore une autre et une meilleure façon est de convertir en un type privé, appelé safe bool idiom.

class A 
{ 
private: 
    struct safe_bool { int true_; }; 
    typedef int safe_bool::*safe_type; 
public: 
    A() {}; 
    operator safe_type() { return &safe_bool::true_; } 
}; 

Retour à la syntaxe: Si vous avez une partie d'autre, vous pouvez utiliser le nom de la variable déclarée, parce qu'elle est encore portée. Il est détruit après que toutes les branches sont traitées avec succès

if(A a = A()) 
{ ... } 
else if(B b = a) 
{ ... } 

Vous pouvez également utiliser le même nom que précédemment, et la variable ombre les autres variables, mais vous ne pouvez pas déclarer le même nom dans le bloc le plus extérieur d'une branche - Il sera en conflit plutôt que de se cacher avec l'autre déclaration.

if(int test = 0) 
{ ... } 
else 
{ int test = 1; /* error! */ } 

La technique de déclarer et initialiser une variable est le plus souvent utilisé avec dynamic_cast bien, mais peut être parfaitement utilisé conjointement avec un utilisateur type défini comme ci-dessus, trop

if(Derived *derived = dynamic_cast<Derived*>(base)) { 
    // do stuff 
} 

Notez que syntaxiquement, vous devez initialiser la variable (en utilisant le formulaire = expression comme pour un argument par défaut). Ce qui suit n'est pas valide

if(ifstream ifs("file.txt")) { 
    // invalid. Syntactic error 
} 
+0

Non seulement "initialize" - quelque chose comme 'if (A a (123))' serait également invalide même en présence d'un constructeur correspondant. Tout comme 'for', il nécessite une forme d'initialisation de copie avec' = '. –

+0

Oui, c'est vrai. Je vais clarifier :) Cela dit, quelque chose comme 'pour (A a (123);;);' est parfaitement valide mais à moins que vous vouliez dire 'pour (; A a (123););' :) –

+0

Heh, je didn Je ne me rends pas compte que vous pouvez écrire cela, mais vous êtes là. Aussi, +1 pour mentionner comment c'est le plus pratique quand 'dynamic_cast' est impliqué (et je trouve étrange que tant de développeurs C++ semblent ne pas être conscients de cet idiome). –

2

La réponse à votre première question est "oui", mais votre "appel constructeur" est erroné. A a déclare une variable. C'est une déclaration, pas une expression. A() est une expression qui construit une instance temporaire anonyme A:

struct A 
{ 
    A() {}; 
    operator bool() { return true; } 
}; 

int main() 
{ 
    if (A()) 
    { 
     // do stuff 
    } 
} 

Si vous voulez utiliser l'instance de A dans "choses", alors vous avez besoin:

if (A a = A()) { 
    // do stuff 
} 
+2

En fait, la réponse à la deuxième question est "oui": ce qui suit est parfaitement légal C++: 'if (A a = A()) {/ * utilise un * /}' –

+0

Bon point, bien fait. Je vais éditer. –

2

Vous pouvez le faire, mais uniquement lorsque vous utilisez la syntaxe d'initialisation de copie pour appeler votre constructeur. Par exemple:

main() 
{ 
    if (A a = A()) 
    { 
     // do stuff 
    } 
} 

La plupart des compilateurs seraient elide le constructeur de copie dans un tel initialiseur lorsque les optimisations sont activées, mais un constructeur accessible est nécessaire quand même.

aussi, naturellement, si A aurait un constructeur tel que A(int), vous pouvez aussi le faire:

if (A a = 123) ... 

En outre, il est généralement considéré comme une mauvaise idée d'avoir operator bool() votre classe à de telles fins. La raison est que bool est implicitement convertible en n'importe quel type numérique (avec false == 0 && true == 1); Par exemple, le client de votre classe peut écrire:

int x = A(); 

void foo(float y); 
foo(A()); 

ce qui n'est probablement pas quelque chose que vous voulez autoriser. Une astuce simple consiste à utiliser un lieu pointeur à membre-du-privé classe:

class A { 
private: 
    struct dummy_t { int dummy; }; 
    typedef int dummy_t::*unspecified_boolean_type; 
public: 
    operator unspecified_boolean_type() const { 
     if (...) { 
      return &dummy_t::dummy; // true 
     } else { 
      return 0; // false 
     } 
    } 
}; 

Pointeurs à des membres ont une conversion bool implicite (comme pointeur habituelle, null est false, toute autre chose est vrai), mais ils ne sont compatibles avec aucun autre type que le leur; et, puisque la classe interne ici est privée, aucun code client ne peut éventuellement déclarer une variable de ce type (auto et decltype en C++ 0x fournir un moyen, cependant). En remarque, main() comme écrit n'est pas valide C++ - ISO C++ n'a pas la règle "int par défaut" comme le fait C, et une fonction sans type de retour explicite n'est pas valide.

+0

"main() tel qu'écrit n'est pas valide C++" - et une définition de classe nécessite un point-virgule , et l'opérateur bool() n'a pas de type de retour, et il (comme le constructeur de A) est privé. –

+1

Euh, pendant que j'améliorais ma réponse, vous avez aussi créé safe-bool. Haha +1 pour une bonne réponse tho xD –

+0

La modification la plus récente est incorrecte, cependant. La liaison d'une référence const à un rvalue nécessite toujours un constructeur de copie. L'implémentation peut décider d'élider la copie, mais le constructeur de copie doit toujours être appelable. Voir le DR suivant: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#391 et http: //www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html # 291 (il s'agit juste de clarifier le libellé - il ne s'agit pas d'exiger une copie de ctor ou autre). Si vous testez, veillez à désactiver le mode C++ 0x car, en C++ 0x, aucun constructeur de copie n'est appelé ou requis. –

0

Si vous essayez d'indiquer l'échec, pourquoi ne pas throw an exception?

#include <stdexcept> 

class Foo 
{ 
public: 
    Foo(void) 
    { 
     if (/* something bad D: */) 
     { 
      throw std::runtime_error("Couldn't open file, etc..."); 
     } 
    } 
} 

int main(void) 
{ 
    try 
    { 
     Foo f; 
     // do stuff with f 
    } 
    catch (std::exception& e) 
    { 
     std::cerr << "Error: " << e.what() << std::endl; 
    } 
} 
+0

Dans le bel environnement intégré dans lequel je développe, les exceptions ne sont tout simplement pas un luxe que nous avons. Les modèles sont à propos de la chose la plus folle que notre compilateur nous permet de faire. –

+0

Ah :([15chars] – GManNickG

Questions connexes