Si j'ai deux variables de membres constants différentes, qui doivent toutes deux être initialisées en fonction du même appel de fonction, existe-t-il un moyen de le faire sans appeler la fonction deux fois?
Par exemple, une classe de fraction où le numérateur et le dénominateur sont constants.
int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
// Lets say we want to initialize to a reduced fraction
Fraction(int a, int b) : numerator(a/gcd(a,b)), denominator(b/gcd(a,b))
{
}
private:
const int numerator, denominator;
};
Cela se traduit par une perte de temps, car la fonction GCD est appelée deux fois. Vous pouvez également définir un nouveau membre de classe gcd_a_b
, et affecter d'abord la sortie de gcd à celle de la liste d'initialisation, mais cela entraînerait un gaspillage de mémoire.
En général, existe-t-il un moyen de le faire sans appels de fonctions ou mémoire gaspillés? Pouvez-vous peut-être créer des variables temporaires dans une liste d'initialisation? Je vous remercie.
-O3
. Mais probablement pour toute implémentation de test simple, cela entraînerait en fait l'appel de fonction. Si vous utilisez__attribute__((const))
ou pur sur le prototype sans fournir de définition visible, il devrait laisser GCC ou clang faire l'élimination de sous-expression commune (CSE) entre les deux appels avec le même argument. Notez que la réponse de Drew fonctionne même pour les fonctions non pures, c'est donc beaucoup mieux et vous devez l'utiliser à tout moment où la fonction pourrait ne pas être en ligne.Réponses:
Oui. Cela peut être fait avec un constructeur délégué , introduit en C ++ 11.
Un constructeur délégué est un moyen très efficace d'acquérir les valeurs temporaires nécessaires à la construction avant l' initialisation des variables membres.
la source
.h
), même si la vraie définition du constructeur n'est pas visible pour l'inline. c'est-à-dire que l'gcd()
appel serait inséré dans chaque site d'appel de constructeur et ne laisserait qu'uncall
au constructeur privé à 3 opérandes.Les vars membres sont initialisés par l'ordre dans lequel ils sont déclarés dans la classe decleration, vous pouvez donc effectuer les opérations suivantes (mathématiquement)
Pas besoin d'appeler un autre constructeur ou même de le faire.
la source
Fraction(a,b,gcd(a,b))
délégation dans l'appelant, ce qui réduit le coût total. Cette intégration est plus facile à faire pour le compilateur que pour annuler la division supplémentaire. Je ne l'ai pas essayé sur godbolt.org mais vous pourriez si vous êtes curieux. Utilisez gcc ou clang-O3
comme le ferait une construction normale. (C ++ est conçu autour de l'hypothèse d'un compilateur d'optimisation moderne, d'où des fonctionnalités commeconstexpr
)@Drew Dormann a donné une solution similaire à ce que j'avais en tête. Puisque OP ne mentionne jamais de ne pas pouvoir modifier le ctor, cela peut être appelé avec
Fraction f {a, b, gcd(a, b)}
:Seulement de cette façon, il n'y a pas de second appel à une fonction, constructeur ou autre, donc ce n'est pas une perte de temps. Et ce n'est pas une mémoire perdue, car un temporaire devrait être créé de toute façon, vous pouvez donc aussi en faire bon usage. Cela évite également une division supplémentaire.
la source
const
, mais fonctionne au moins pour d'autres types. Et quelle division supplémentaire évitez-vous «également»? Vous voulez dire par rapport à la réponse de asmmo?,gcd(foo, bar)
est un code supplémentaire qui pourrait et devrait donc être pris en compte dans chaque site d'appel de la source . C'est un problème de maintenabilité / lisibilité, pas de performances. Le compilateur l'inclura très probablement au moment de la compilation, ce que vous souhaitez pour les performances.Fraction f( x+y, a+b );
Pour l'écrire à votre façon, vous devez écrireBadFraction f( x+y, a+b, gcd(x+y, a+b) );
ou utiliser des vars tmp. Ou pire encore, que se passe-t-il si vous voulez écrireFraction f( foo(x), bar(y) );
- alors vous auriez besoin que le site d'appel déclare des vars tmp pour contenir les valeurs de retour, ou appelez à nouveau ces fonctions et espère que le compilateur les supprime, ce que nous évitons. Voulez-vous déboguer le cas d'un appelant mélangeant les arguments pourgcd
que ce ne soit pas réellement le GCD des 2 premiers arguments passés au constructeur? Non? Alors ne rendez pas ce bug possible.