Если у меня есть две разные постоянные переменные-члены, которые нужно инициализировать на основе одного и того же вызова функции, есть ли способ сделать это, не вызывая функцию дважды?
Например, класс дроби, где числитель и знаменатель постоянны.
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;
};
Это приводит к потере времени, так как функция GCD вызывается дважды. Вы также можете определить новый член класса gcd_a_b
и сначала назначить вывод gcd в списке инициализаторов, но затем это приведет к потере памяти.
В общем, есть ли способ сделать это без напрасных вызовов функций или памяти? Можете ли вы создать временные переменные в списке инициализаторов? Спасибо.
-O3
. Но, вероятно, для любой простой реализации теста это на самом деле встроит вызов функции. Если вы используете__attribute__((const))
или чистите прототип без предоставления видимого определения, он должен позволить GCC или clang выполнять исключение общих подвыражений (CSE) между двумя вызовами с одним и тем же аргументом. Обратите внимание, что ответ Дрю работает даже для не чистых функций, поэтому он намного лучше, и вы должны использовать его в любое время, когда функция может не быть встроенной.Ответы:
Да. Это можно сделать с помощью делегирующего конструктора , представленного в C ++ 11.
Делегирование конструктор является очень эффективным способом получить временные значения , необходимые для строительства , прежде чем какие - либо переменные членов инициализируются.
источник
.h
), даже если реальное определение конструктора не видно для встраивания. т. е.gcd()
вызов будет встроен в каждый сайт вызова конструктора и оставит толькоcall
3-операндный приватный конструктор.Элементы-члены инициализируются в порядке их объявления в объявлении класса, поэтому вы можете сделать следующее (математически)
Не нужно вызывать других конструкторов или даже создавать их.
источник
Fraction(a,b,gcd(a,b))
делегирование в вызывающую сторону, что приведет к снижению общей стоимости. Этот компилятор легче сделать компилятору, чем отменить дополнительное деление в этом. Я не пробовал это на godbolt.org, но вы можете, если вам интересно. Используйте gcc или clang,-O3
как при обычной сборке. (C ++ разработан исходя из предположения о современном оптимизирующем компиляторе, следовательно, имеет такие функции, какconstexpr
)@ Дрю Дорманн дал решение, подобное тому, что я имел в виду. Поскольку OP никогда не упоминает о невозможности изменить ctor, это можно вызвать с помощью
Fraction f {a, b, gcd(a, b)}
:Только так нет второго вызова функции, конструктора или чего-то еще, так что это не потерянное время. И это не потерянная память, поскольку временное хранилище должно быть создано в любом случае, так что вы также можете использовать его с пользой. Это также позволяет избежать дополнительного деления.
источник
const
, но, по крайней мере, работает для других типов. И какого дополнительного разделения вы «также» избегаете? Вы имеете в виду против ответа Асммо?,gcd(foo, bar)
является дополнительным кодом, который может и поэтому должен быть выделен из каждого места вызова в источнике . Это проблема удобства сопровождения / читаемости, а не производительности. Компилятор, скорее всего, встроит его во время компиляции, которое вы хотите для производительности.Fraction f( x+y, a+b );
чтобы написать это по-своему, вам придется написатьBadFraction f( x+y, a+b, gcd(x+y, a+b) );
или использовать tmp vars. Или, что еще хуже, что если вы захотите написатьFraction f( foo(x), bar(y) );
- тогда вам понадобится сайт вызова, чтобы объявить некоторые переменные tmp для хранения возвращаемых значений, или снова вызвать эти функции и надеяться, что компилятор CSE удалит их, чего мы и избегаем. Вы хотите отладить случай, когда один вызывающий объект смешивает аргументы, чтобыgcd
на самом деле это не GCD первых двух аргументов, переданных конструктору? Нет? Тогда не делайте эту ошибку возможной.