web-dev-qa-db-fra.com

"Downcasting" unique_ptr <Base> à unique_ptr <Derived>

J'ai une série d'usines qui retournent unique_ptr<Base>. Sous le capot, cependant, ils fournissent des pointeurs vers divers types dérivés, à savoir unique_ptr<Derived>, unique_ptr<DerivedA>, unique_ptr<DerivedB>etc.

Donné DerivedA : Derived et Derived : Base nous aurions:

unique_ptr<Base> DerivedAFactory() {
    return unique_ptr<Base>(new DerivedA);
}

Ce que je dois faire est de "lancer" le pointeur à partir du unique_ptr<Base> à un niveau dérivé (pas nécessairement le niveau interne d'origine). Pour illustrer en pseudo code:

unique_ptr<Derived> ptr = static_cast<unique_ptr<Derived>>(DerivedAFactory());

Je pense à le faire en libérant l'objet du unique_ptr, puis en utilisant une fonction qui convertit le pointeur brut et le réaffecte à un autre unique_ptr de la saveur souhaitée (le release serait explicitement fait par l'appelant avant l'appel):

unique_ptr<Derived> CastToDerived(Base* obj) {
    return unique_ptr<Derived>(static_cast<Derived*>(obj));
}

Est-ce valable, ou y aura-t-il quelque chose de génial?


PS. Il y a une complication supplémentaire en ce que certaines des usines résident dans des DLL qui sont chargées dynamiquement au moment de l'exécution, ce qui signifie que je dois m'assurer que les objets produits sont détruits dans le même contexte (espace de tas) que ils ont été créés. Le transfert de propriété (qui se produit généralement dans un autre contexte) doit alors fournir un deleter à partir du contexte d'origine. Mais en plus d'avoir à fournir/cast un deleter avec le pointeur, le problème de casting doit être le même.

53
d7samurai

Je créerais quelques modèles de fonction, static_unique_ptr_cast et dynamic_unique_ptr_cast. Utilisez le premier dans les cas où vous êtes absolument certain que le pointeur est en fait un Derived *, sinon utilisez ce dernier.

template<typename Derived, typename Base, typename Del>
std::unique_ptr<Derived, Del> 
static_unique_ptr_cast( std::unique_ptr<Base, Del>&& p )
{
    auto d = static_cast<Derived *>(p.release());
    return std::unique_ptr<Derived, Del>(d, std::move(p.get_deleter()));
}

template<typename Derived, typename Base, typename Del>
std::unique_ptr<Derived, Del> 
dynamic_unique_ptr_cast( std::unique_ptr<Base, Del>&& p )
{
    if(Derived *result = dynamic_cast<Derived *>(p.get())) {
        p.release();
        return std::unique_ptr<Derived, Del>(result, std::move(p.get_deleter()));
    }
    return std::unique_ptr<Derived, Del>(nullptr, p.get_deleter());
}

Les fonctions prennent une référence de valeur pour vous assurer que vous ne tirez pas le tapis sous les pieds de l'appelant en volant le unique_ptr passé à vous.

41
Praetorian