Отличаются тем, что во втором случае объект по ссылке изменять нельзя. От возврата по значению отличаются тем, что при возврате по значению происходит копирование объекта со всеми вытекающими:
1. Это может быть накладно
2. Результат - это копия, поэтому изменения, внесённый в неё - это изменения в копии, а не в том объекте, который возвращался. По этой причине, например, operator [] у класса-массива должен возвращать ссылку, чтобы изменяя полученную ссылку, изменялось бы значение, которое лежит в массиве.
Ссылка в этом смысле не отличается от указателя, кроме того, что не может быть null. Поэтому нельзя, например, возвращать ссылку на какую-то временную переменную:
int & foo()
{
int x = 100;
return x;
}
int main()
{
int & y = foo();
y = 10; // UB
}
Переменная x тут умрёт по выходе из foo, и y будет ссылаться на уже мёртвую переменную. Но можно возвращать ссылку на член класса, если объект класса будет жить дольше, чем ссылка. Т.е. так можно:
class Test
{
int x;
public:
int & getx() { return x; }
};
int main()
{
Test t;
int & y = t.getx();
y = 10; // t.x будет равен 10
}
А вот так опять нет:
class Test
{
int x;
public:
int & getx() { return x; }
};
int main()
{
int * y;
{
Test t;
y = &t.getx();
}
*y = 10; // UB, t умирает в конце блока, x тоже, а обращение идёт уже после
}