Да, вам все же придется расплачиваться за вызов конструктора, поскольку память, выделяемая new, инициализируется вызовом соответствующего конструктора, но теперь возникает новая проблема: кто выполнит delete для объекта, созданного вами с использованием new? Даже если вызывающая программа написана аккуратно и добросовестно, не вполне понятно, как она предотвратит утечку в следующем вполне естественном сценарии:
Rational w, x, y, z; w = x * y * z; // то же, что operator*(operator*(x, y), z)
Здесь выполняется два вызова operator* в одном предложении, поэтому получаются два вызова new, которым должны соответствовать два delete. Но у пользователя operator* нет возможности это сделать, так как он не может получить указатели, скрытые за ссылками, которые возвращает функция operator*. Это гарантированная утечка ресурсов. Но, возможно, вы заметили, что оба подхода (на основе стека и на основе кучи) страдают от необходимости вызова конструкторов для каждого возвращаемого значения operator*. Вспомните, что исходно мы ставили себе целью вообще не вызывать конструкторы. Быть может, вы думаете, что знаете, как избежать всего, всех вызовов конструктора, кроме одного. Не исключено, что вы придумали следующую реализацию функции operator*, которая возвращает ссылку на
const Rational& operator*(const Rational& lhs, // предупреждение!
const Rational& rhs) // Код еще хуже!
{
static Rational result; // статический объект,
// на который возвращается ссылка
result = …; // умножить lhs на rhs и поместить
// произведение в result
return result;
}Подобно всем проектным решениям на основе статических объектов, это сразу вызывает вопросы, связанные с безопасностью относительно потоков, но есть и более очевидный недостаток. Чтобы разглядеть его, рассмотрим следующий абсолютно разумный код:
bool operator==(const Rational& lhs, // оператор == для Rational
const Rational& rhs);
Rational a, b, c, d;
…
if ((a*b) == (c*d)) {
} else {
Догадываетесь, что не так? Выражение ((a*b) == (c*d)) будет
if(operator==(operator*(a, b), operator*(c, d)))