2009-09-26 4 views
19

Imaginez deux pièces similaires de code:Quelle est la différence entre lancer et lancer avec arg d'exception interceptée?

try { 
    [...] 
} catch (myErr &err) { 
    err.append("More info added to error..."); 
    throw err; 
} 

et

try { 
    [...] 
} catch (myErr &err) { 
    err.append("More info added to error..."); 
    throw; 
} 

Sont-ils effectivement les mêmes ou diffèrent-ils d'une manière subtile? Par exemple, le premier fait-il exécuter un constructeur de copie alors que le second réutilise peut-être le même objet pour le relancer?

Répondre

26

Selon la façon dont vous avez organisé votre hiérarchie d'exception, re-lancer une exception en nommant la variable d'exception dans l'instruction throw peut tranche l'objet d'exception d'origine.

Une expression throw sans argument jeter l'objet d'exception actuelle en préservant son type dynamique, alors qu'une expression de jet avec un argument lancer une nouvelle exception en fonction du type statique de l'argument à throw.

E.g.

int main() 
{ 
    try 
    { 
     try 
     { 
      throw Derived(); 
     } 
     catch (Base& b) 
     { 
      std::cout << "Caught a reference to base\n"; 
      b.print(std::cout); 
      throw b; 
     } 
    } 
    catch (Base& b) 
    { 
     std::cout << "Caught a reference to base\n"; 
     b.print(std::cout); 
    } 

    return 0; 
} 

Comme écrit ci-dessus, le programme de sortie:

Caught a reference to base 
Derived 
Caught a reference to base 
Base

Si le throw b est le remplacer par un throw, puis la prise extérieure attrapera également l'exception Derived initialement lancée. Cela reste valable si la classe interne intercepte l'exception Base par valeur plutôt que par référence - bien que cela signifie naturellement que l'objet d'exception original ne peut pas être modifié, donc aucune modification à b ne sera reflétée dans l'exception Derived interceptée par le bloc externe .

+1

Ah, j'ai complètement oublié de trancher! Bon sang, c'est important! Merci d'avoir soulevé cette question. +1 (Bien que je pense quand vous avez écrit "... en préservant le type statique original ...", vous vouliez dire _dynamic_ type, ce qui s'appelle _dynamic type_, après tout, sinon le _ "type statique original" _.) - – sbi

+1

Great répondre, j'ai complètement oublié à ce sujet aussi. – GManNickG

+0

Je suis heureux que quelqu'un d'autre a rencontré le problème _slicing_;) –

16

Dans le second cas selon C++ constructeur de copie standard 15,1/6 est pas utilisé:

Une touche expression sans opérande rethrows l'exception en cours de traitement. L'exception est réactivée avec le temporaire existant; aucun nouvel objet d'exception temporaire n'est créé. L'exception n'est plus considérée comme interceptée. par conséquent, la valeur de uncaught_exception() sera à nouveau vraie.

Dans le premier cas, une nouvelle exception sera levée selon 15.1/3:

Une touche expression initialise un objet temporaire, appelé l'objet d'exception, dont le type est déterminé en supprimant toute cv-qualificateurs de haut niveau du type statique de l'opérande de lancer et d'ajuster le type de "tableau de T" ou "fonction retournant T" à "pointeur sur T" ou "pointeur sur la fonction retournant T", respectivement. < ...> Le temporaire est utilisé pour initialiser la variable nommée dans le gestionnaire correspondant (15.3). Le type de l'expression-projection ne doit pas être un type incomplet, ou un pointeur ou une référence à un type incomplet, autre que void *, const void *, volatile void *, ou const volatile void *. À l'exception de ces restrictions et des restrictions sur l'appariement de type mentionnées en 15.3, l'opérande de throw est traité exactement comme un argument de fonction dans un appel (5.2.2) ou l'opérande d'une instruction return.

Dans les deux cas constructeur de copie est nécessaire à l'étape de projection (15.1/5):

Lorsque l'objet jeté est un objet de classe, et le constructeur de copie utilisé pour initialiser la copie temporaire n'est pas accessible , le programme est mal formé (même si l'objet temporaire pourrait être éliminé). De même, si le destructeur de cet objet n'est pas accessible, le programme est mal formé (même si l'objet temporaire pourrait être éliminé).

+0

Étant donné que la copie ctor doit être accessible pour l'exception levée à l'origine, je ne pense pas que ce soit un problème dans ce cas. Mais le tien est toujours une bonne réponse. +1 – sbi

+0

Oui, mais dans le premier cas, la copie c-tor sera utilisée deux fois. –

+1

Il indique quand la déclaration spécifie un type de classe. Cependant dans son cas il spécifie un type de référence :) La référence sera liée directement et se référera à l'objet d'exception. Donc, nous aurons besoin d'une copie ctor seulement au point de lancer, ne pas attraper dans ce cas (cela ferait une différence quand un constructeur de copie de base est protégé, par exemple). –

Questions connexes