Подтвердить что ты не робот

Безопасно ли возвращать * это как ссылку?

Возвращаемая ссылка на этот объект часто используется в перегрузке оператора присваивания. Он также используется в качестве базы для именованных параметров idiom, который позволяет инициализировать объект по цепочке вызовов методам setter: Params().SetX(1).SetY(1) каждый из которых возвращает ссылку на * this.

Но правильно ли возвращать ссылку на *this. Что делать, если мы вызываем метод, возвращающий ссылку на это для временного объекта:

#include <iostream>

class Obj
{
public:
    Obj(int n): member(n) {}
    Obj& Me() { return *this; }

    int member;
};

Obj MakeObj(int n)
{
    return Obj(n);
}

int main()
{
    // Are the following constructions are correct:
    std::cout << MakeObj(1).Me().member << std::endl;
    std::cout << Obj(2).Me().member << std::endl;
    Obj(3).Me() = Obj(4);

    return 0;
}
4b9b3361

Ответ 1

Да, безопасно вернуть * это. Простым случаем является то, что это не временное, хотя даже когда оно есть, это должно быть возможным:

Временные объекты уничтожаются как последний шаг при оценке полного выражения (1.9), который (лексически) содержит точку, в которой они были созданы. Это справедливо, даже если эта оценка заканчивается выдачей исключения (С++ 03 §12.2/3).

Другими словами, пока вы не достигнете полуколоны, все должно быть хорошо (теоретически).

Поэтому следующий код должен работать:

std::cout << MakeObj(1).Me().member << std::endl;

Пока это не должно работать:

const Obj &MakeMeObj(int n) { return Obj(n).Me(); }
std::cout << MakeMeObj(1).member << std::endl;

Это логично, поскольку вы возвращаете ссылку на временную. Большинство компиляторов предупреждают об этом, но если код становится сложным, это то, на что нужно обратить внимание.

Лично я бы запретил называть эти методы на временном объекте, чтобы заставить пользователей API думать о времени жизни объекта. Что можно сделать, перегружая ваш метод: (Если ваш компилятор уже поддерживает его)

Obj &Me() & { return *this; }
Obj &Me() && = delete;

Ответ 2

// Are the following constructions are correct:
std::cout << MakeObj(1).Me().member << std::endl;
std::cout << Obj(2).Me().member << std::endl;

Да, поскольку в каждой строке время жизни всех временных объектов расширяется, чтобы принять полное выражение.

Как cppreference.com говорит:

(...) все временные объекты уничтожаются как последний шаг в оценивая полное выражение, которое (лексически) содержит точку где они были созданы (...).

Если вы попытаетесь разделить полное выражение, то вы (надеюсь) получите ошибку компилятора или предупреждение:

// not allowed:
Obj& ref = MakeObj(1);
std::cout << ref.Me().member << std::endl;

В других случаях компилятор может быть недостаточно умным, чтобы увидеть проблему, создать свой исполняемый файл без предоставления диагностического сообщения и, в конечном итоге, создать поведение undefined в вашей программе:

// undefined behaviour:
Obj &ref = MakeObj(1).Me();
std::cout << ref.member << std::endl;

Ответ 3

Да, это безопасно. Временная жизнь объекта будет до конца заявления (точнее, оценка полного выражения, в котором оно создано). Это гарантируется стандартом:

12.2/3: Временные объекты уничтожаются как последний шаг при оценке полного выражения, которое (лексически) содержит где они были созданы.

Временное время жизни может даже быть расширено при некоторых условиях, если оно связано с ссылкой. Но не ожидайте здесь чудес. Пытаясь сохранить ссылку за пределами утверждения (f.ex, взяв адрес или присвоив ссылку), можно быстро привести к UB (demo).

Если вы будете использовать этот тип конструкции в объектах const, у вас также возникнут проблемы, так как вы попытаетесь вернуть ref const ref (но это не относится к вашим примерам для назначения и сеттеры).