J'ai obtenu un non-trivial type qui possède de multiples ressources. Comment puis-je construire une exception sécurité?
Par exemple, voici une démo de la classe X
qui est titulaire d'un tableau des A
:
#include "A.h"
class X
{
unsigned size_ = 0;
A* data_ = nullptr;
public:
~X()
{
for (auto p = data_; p < data_ + size_; ++p)
p->~A();
::operator delete(data_);
}
X() = default;
// ...
};
Maintenant, la réponse la plus évidente de cette classe est d' utiliser std::vector<A>
. Et que de bons conseils. Mais X
n'est qu'un stand-in pour les scénarios plus complexes où l' X
doit posséder de multiples ressources et il n'est pas commode d'utiliser les bons conseils de "utiliser les std::lib." J'ai choisi de communiquer sur la question avec cette structure de données simplement parce qu'il est familier.
Pour être en cristal clair: Si vous pouvez concevoir votre X
telle qu'un défaut de paiement ~X()
correctement nettoie tout ("la règle du zéro"), ou si ~X()
n'a qu'à sortir une seule ressource, alors qui est le meilleur. Cependant, il y a des moments dans la vie réelle lorsqu' ~X()
a affaire à de multiples ressources, et cette question répond à ces circonstances.
Donc, ce type a déjà une bonne destructeur, et un bon constructeur par défaut. Ma question est axé sur une non-trivial constructeur qui prend deux A
s', alloue de l'espace pour eux, et les constructions eux:
X::X(const A& x, const A& y)
: size_{2}
, data_{static_cast<A*>(::operator new (size_*sizeof(A)))}
{
::new(data_) A{x};
::new(data_ + 1) A{y};
}
J'ai entièrement instrumenté de la classe de test A
et si aucune exception n'est levée à partir de ce constructeur, il fonctionne parfaitement. Par exemple avec ce test pilote:
int
main()
{
A a1{1}, a2{2};
try
{
std::cout << "Begin\n";
X x{a1, a2};
std::cout << "End\n";
}
catch (...)
{
std::cout << "Exceptional End\n";
}
}
La sortie est:
A(int state): 1
A(int state): 2
Begin
A(A const& a): 1
A(A const& a): 2
End
~A(1)
~A(2)
~A(2)
~A(1)
J'ai 4 constructions, et 4 de destructions, et de chaque destruction correspond à un constructeur. Tout est bien.
Toutefois, si le constructeur de copie d' A{2}
déclenche une exception, j'obtiens ce résultat:
A(int state): 1
A(int state): 2
Begin
A(A const& a): 1
Exceptional End
~A(2)
~A(1)
Maintenant, j'ai 3 constructions mais seulement 2 des destructions. L' A
résultant de l' A(A const& a): 1
a été coulé!
Une façon de résoudre ce problème est de la dentelle le constructeur try/catch
. Toutefois, cette approche n'est pas évolutif. Après chaque seul d'allocation de ressources, j'ai besoin d'encore un autre imbriquée try/catch
pour tester la prochaine allocation des ressources et de libérer ce qui a déjà été alloués. Détient le nez:
X(const A& x, const A& y)
: size_{2}
, data_{static_cast<A*>(::operator new (size_*sizeof(A)))}
{
try
{
::new(data_) A{x};
try
{
::new(data_ + 1) A{y};
}
catch (...)
{
data_->~A();
throw;
}
}
catch (...)
{
::operator delete(data_);
throw;
}
}
Cette correctement les résultats:
A(int state): 1
A(int state): 2
Begin
A(A const& a): 1
~A(1)
Exceptional End
~A(2)
~A(1)
Mais c'est moche! Que faire si il y a 4 ressources? Ou 400?! Que faire si le nombre de ressources est pas connu au moment de la compilation?!
Est-il un meilleur moyen?