Является ли время жизни ссылки расширенным?

Я хочу передать ссылку в функцию. Этот код не работает, как я ожидал:

struct A {
};

void foo(A& a) {
    // do something with a
}

int main(int, char**) {
    foo(A());
}

Я получаю ошибку компиляции

неверная инициализация неконстантной ссылки типа A& из rvalue типа A

Но когда я просто добавляю метод A& ref() в A, как показано ниже, и вызываю его, прежде чем передавать его, кажется, я могу использовать A. При отладке объект A уничтожается после вызова foo():

struct A {
    A& ref() {
        return *this;
    }
};

void foo(A& a) {
    // do something with a
}

int main(int, char**) {
    foo(A().ref());
}

Является ли этот действующий код стандартным? Вызывает ли вызов ref() время жизни объекта до тех пор, пока foo() не вернется?

Ответы

Ответ 1

Ваш код отлично работает.

В этой строке

foo(A().ref());

Экземпляр временного A живет до конца инструкции (;).

Для чего безопасно передать A&, возвращенный с ref() в foo (пока foo не сохраняет его).

ref() сам по себе не продлевает время жизни, но помогает, возвращая ссылку lvalue.

Что происходит в случае foo(A());? Здесь временное значение передается как rvalue. И в С++ rvalue не связывается с неконстантными ссылками lvalue (даже в С++ 11, ссылка rvalue не связывается с неконстантными ссылками lvalue).

Из этой статьи в блоге Visual С++ о ссылках rvalue:

... С++ не хочет, чтобы вы случайно изменяли временные, но напрямую вызов функции non-const-члена на изменяемом rvalue является явным, поэтому это позволило...

Ответ 2

A() создает временный объект типа A. Объект существует до конца полного выражения, в котором он создается. Проблема в вашем исходном коде - не время жизни этого временного; это то, что функция принимает свой аргумент как неконстантную ссылку, и вам не разрешается передавать временный объект как неконстантную ссылку. Самое простое изменение для foo, чтобы принять его аргумент по ссылке const, если это соответствует тому, что делает функция:

void foo(const A&);
int main() {
    foo(A());
}

Ответ 3

В этом вопросе есть несколько вопросов. Я попытаюсь обратиться ко всем из них:


Во-первых, вы не можете передать временное (prvalue) типа A в функцию, принимающую A&, потому что ссылки не-const lvalue не могут связываться с rvalues. Это языковое ограничение. Если вы хотите иметь возможность пройти временное, вам нужно либо взять параметр типа A&&, либо тип A const& - последний, поскольку временные объекты могут связываться с константными ссылками lvalue.


Является ли этот действующий код стандартным? Вызывает ли вызов ref() магическое время жизни объекта до тех пор, пока foo() не вернется?

В вашей программе вообще нет продления жизни. Из [class.temp]:

Существует три контекста, в которых временные объекты уничтожаются в другой точке, чем конец полного выражения. Первый контекст - это когда конструктор по умолчанию вызывается для инициализации элемента массива с помощью нет соответствующего инициализатора (8.6). Второй контекст - это когда конструктор копирования вызывается для копирования элемента массива, в то время как весь массив копируется (5.1.5, 12.8). [...] Третий контекст - это когда привязка привязана к временному.

Ни один из этих контекстов не применяется. Мы никогда не связываем ссылку на временный код. ref() привязывает *this к A&, но *this не является временным, и затем эта ссылка просто передается в foo().

Рассмотрим этот вариант вашей программы:

#include <iostream>

struct A {
    A& ref() { return *this; }
    ~A() { std::cout << "~A()\n"; }
};

int main() {
    auto& foo = A().ref();
    std::cout << "----\n";
}

который печатает

~A()
----

иллюстрирующий отсутствие продления срока службы.


Если вместо привязки результата ref() к ссылке мы привязали элемент:

#include <iostream>

struct A {
    A& ref() { return *this; }
    int x;

    ~A() { std::cout << "~A()\n"; }
};

int main() {
    auto&& foo = A().x;
    std::cout << "----\n";
}

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

----
~A()