Тернарный оператор и продление жизни временного объекта с помощью ссылки на константу

Увидев, что локальная ссылка на константу может продлить жизнь временного, я столкнулся с необходимостью условно привязать локальную ссылку-к-константе либо к параметру функции, либо к временному результату вызова функции, т.е.:

class Gizmo
{
    // Rule of Five members implemented
};

Gizmo Frobnicate(const Gizmo& arg);

void ProcessGizmo(const Gizmo& arg, bool frobnicate)
{
    const Foo& local = frobnicate ? Frobnicate(arg) : arg;
    // Perform some work on local
}

Практический пример: логическое значение указывает, следует ли сжимать буфер, и вы хотите написать унифицированный код, который работает с local в любом случае.

Однако приведенный выше пример вызывал конструктор копирования Gizmo для arg, когда frobnicate было false. Мне удалось избежать вызова конструктора копирования, изменив Frobnicate(arg) на static_cast<const Gizmo&>(Frobnicate(arg)).

У меня возникает вопрос: как тернарный оператор взаимодействует с правилом о привязке локальной ссылки к константе к временной? Является ли мое решение законным и корректным?


person DoomMuffins    schedule 21.02.2015    source источник


Ответы (1)


Нет, это не прилично. Чтобы произошло продление срока службы, временный объект должен быть привязан непосредственно к ссылке. Как только вы добавите приведение, привязка перестанет быть прямой, и результирующая ссылка станет висячей.

Мы можем увидеть это с помощью простого тестового кода:

#include <iostream>
struct Gizmo
{
    ~Gizmo() { std::cout << "Gizmo destroyed\n"; }
};

Gizmo Frobnicate(const Gizmo& arg) { return arg; }

void ProcessGizmo(const Gizmo& arg, bool frobnicate)
{
    const Gizmo& local = frobnicate ? static_cast<const Gizmo&>(Frobnicate(arg)) : arg;
    // Perform some work on local
    (void) local;
    std::cout << "Processing\n";
}

int main(){
    Gizmo g;
    ProcessGizmo(g, true);
    std::cout << "Processed\n";
}

Это выводит:

Gizmo destroyed
Processing
Processed
Gizmo destroyed

Это первое сообщение Gizmo destroyed получено из возвращаемого значения Frobnicate(); он уничтожается в конце этой строки - без продления срока службы.

Очевидным обходным путем является перемещение обработки в другую функцию:

void DoProcessGizmo(const Gizmo& arg) { /* process the Gizmo */ }

void ProcessGizmo(const Gizmo& arg, bool frobnicate)
{
    return frobnicate ? DoProcessGizmo(Frobnicate(arg)) : DoProcessGizmo(arg);
}
person T.C.    schedule 21.02.2015
comment
Как насчет return DoProcessGizmo(frobnicate? Frobnicate(arg): arg); Это также работает, как и ожидалось - person dats; 02.10.2018
comment
Это создает копию, которую ОП пытается избежать. - person T.C.; 02.10.2018
comment
Хм... ты прав! Но я почти уверен, что видел, как эту копию избегали в какой-то версии MSVC в прошлом ... Не то чтобы я мог воспроизвести ее с последней версией. Спасибо! Я должен пойти и избежать некоторых копий сейчас! - person dats; 04.10.2018
comment
А как насчет return DoProcessGizmo( frobnicate ? static_cast< const Gizmo & >( Frobnicate( arg ) ) : arg );? Я предполагаю, что это допустимый C++, без неопределенного поведения (хотя и более уродливого, чем наличие двух независимых вызовов DoProcessGizmo). - person jchl; 31.07.2020