28 votes

Utilisation de traits de type C ++ 11 pour fournir d'autres implémentations en ligne

Est le code suivant motif raisonnable lors de l'utilisation de traits d'basé sur un modèle de code où les deux implémentations alternatives sont toujours compilable?

La lecture du code semble plus clair que de faire d'autres manigances pour conditionnellement compiler (mais peut-être que je suis juste pas assez familiers avec ces manigances).

template<typename T>
class X
{
    void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value)
    {
        if (std::is_nothrow_copy_constructible<T>::value)
        {
            // some short code that assumes T's copy constructor won't throw
        }
        else
        {
            // some longer code with try/catch blocks and more complexity
        }
    }

    // many other methods
};

(La complexité est en partie pour fournir la forte exception de garantie.)

Je sais que ce code de travail, mais est-il raisonnable de s'attendre à ce que le compilateur pour éliminer la constante de fausses branches et ne l'in-lining, etc pour la noexcept des cas beaucoup plus simple que les autres cas)? Je suis en espérant quelque chose qui serait aussi efficace dans les noexcept cas que l'écriture de la méthode avec seulement ce premier bloc que le corps (et vice-versa, même si je suis moins inquiet sur le cas complexe).

Si ce n'est pas la bonne façon de le faire, quelqu'un peut-il svp m'éclairer à la syntaxe recommandée?

21voto

skypjack Points 5516

[...] est-il raisonnable de s'attendre à ce que le compilateur pour éliminer la constante de fausses branches et ne l'in-lining, etc pour la noexcept des cas beaucoup plus simple que les autres cas)?

Il pourrait l'être, mais je ne voudrais pas compter sur cela pour vous ne peut pas le contrôler.


Si vous souhaitez supprimer l' if/else, vous pouvez sfinae le type de retour et de nettoyer l' noexcept de qualification.
À titre d'exemple:

template<typename T>
class X {
    template<typename U = T>
    std::enable_if_t<std::is_nothrow_copy_constructible<T>::value>
    do_something()
    noexcept(true)
    {}

    template<typename U = T>
    std::enable_if_t<not std::is_nothrow_copy_constructible<T>::value>
    do_something()
    noexcept(false)
    {}
};

Les inconvénients sont que vous avez maintenant les deux fonctions membres template.
Pas sûr qu'il s'adapte à vos exigences.

Si vous êtes autorisé à utiliser les fonctionnalités de C++17, if constexpr est probablement la façon de faire et vous n'avez pas à casser votre méthode en deux fonctions de membre de plus.

Une autre approche pourrait être fondée sur le tag de distribution de l' noexceptness de votre type.
À titre d'exemple:

template<typename T>
class X {
    void do_something(std::true_type)
    noexcept(true)
    {}

    void do_something(std::false_type)
    noexcept(false)
    {}

    void do_something()
    noexcept(do_something(std::is_nothrow_copy_constructible<T>{}))
    { do_something(std::is_nothrow_copy_constructible<T>{}); }
};

Je sais, sfinae n'est pas un verbe, mais à sfinae quelque chose de sons tellement bon.

13voto

sbabbi Points 3366

est-il raisonnable de s'attendre à ce que le compilateur pour éliminer la constante de fausses branches et ne l'in-lining, etc pour la noexcept cas [...]?

Oui. Cela étant dit, la constante false branche doit être instancié, qui pourrait ou ne pourrait pas provoquer le compilateur pour instancier un tas de symboles que vous n'avez pas besoin (et puis vous avez besoin de s'appuyer sur l'éditeur de liens pour supprimer ces).

J'allais encore à l'SFINAE manigances (en fait, la balise de distribution), ce qui peut être fait très facilement en C++11.

template<typename T>
class X
{
    void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value)
    {
        do_something_impl(std::is_nothrow_copy_constructible<T>() ); 
    }

    void do_something_impl( std::true_type /* nothrow_copy_constructible */ )
    {
        // some short code that assumes T's copy constructor won't throw
    }

    void do_something_impl( std::false_type /* nothrow_copy_constructible */)
    {
        // some longer code with try/catch blocks and more complexity
    }

    // many other methods
};

Si vous voulez vérifier pour nothrow_copy_constructor dans toutes les autres méthodes, vous pouvez envisager de se spécialisant l'ensemble de la classe:

template<typename T, class = std::is_nothrow_copy_constructible_t<T> >
class X
{
   //throw copy-ctor implementation
};

template<typename T>
class X<T, std::true_type>
{
   // noexcept copy-ctor implementation
};

8voto

Leon Points 20011

Est-il raisonnable de s'attendre à ce que le compilateur pour éliminer la constante de fausses branches?

Oui, l'élimination du code mort est l'un des plus simples optimisations.

... et de faire l'in-lining, etc pour la noexcept cas?

Mon premier réflexe a été de répondre "Non, vous ne pouvez pas compter sur cela, puisqu'il dépend d'où l'in-lining passe se trouve dans l'optimisation des flux par rapport à l'élimination du code mort étape".

Mais après réflexion, je ne vois pas pourquoi une mature compilateur à un assez haut niveau d'optimisation de ne pas accomplir l'élimination du code mort, à la fois avant et après l'inlining étape. Si cette attente doit être raisonnable aussi.

Cependant, devinant à l'égard des optimisations n'est jamais une chose sûre. Aller de la simple mise en œuvre et d'arriver à correctement le fonctionnement du code. Ensuite, mesurer ses performances et de vérifier si vos hypothèses étaient vraies. S'ils n'étaient pas - reegineering la mise en œuvre de votre situation ne prendra pas beaucoup plus de temps que si vous êtes allé vers le bas de la garantie de chemin depuis le début.

6voto

Yakk Points 31636

Chaque matures compilateur ne l'élimination du code mort. Chaque matures compilateur détecte constante des branches, des morts et des codes de l'autre branche.

Vous pouvez créer une fonction avec une douzaine d'arguments de modèle qui utilise naïf if vérifie dans son corps et de regarder la résultante assumbly -- il ne va pas être un problème.

Si vous faites des choses comme créer static variables ou thread_local ou instancier des symboles, ce sont toutes les plus difficiles à éliminer.

Inline est un peu plus difficile, parce que les compilateurs ont tendance à abandonner l'in-lining, à un certain point; le plus complexe, le code, le plus probable, le compilateur donne avant de l'in-lining-il.

En C++17 vous pouvez mettre à niveau votre if de la constexpr version. Mais en C++14 et 11, votre code fera l'amende juste. Il est plus simple et plus facile à lire que les solutions de rechange.

Il est un peu fragile, mais si elle se casse, elle le fait généralement au moment de la compilation dans le brouhaha de la sorte.

1voto

themagicalyang Points 1041

mais est-il raisonnable de s'attendre à ce que le compilateur élimine les branches constantes-fausses

Non. Toutes les branches seront évaluées par le compilateur. Vous pouvez essayer d'utiliser if constexpr partir de c ++ 17.

Ce que vous essayez d'atteindre, c'est SFINAE.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X