Ответ 1
В вашем конкретном случае вторая перегрузка бесполезна.
С исходным кодом, который имеет только одну перегрузку для Load
, эта функция вызывается для lvalues и rvalues.
С новым кодом первая перегрузка вызывается для lvalues, а вторая вызывается для rvalues. Однако вторая перегрузка вызывает первую. В конце, эффект вызова того или другого подразумевает, что будет выполняться одна и та же операция (независимо от первой перегрузки).
Следовательно, эффекты исходного кода и нового кода одинаковы, но первый код просто проще.
Решая, должна ли функция принимать аргумент по значению, lvalue reference или rvalue reference сильно зависит от того, что он делает. Вы должны предоставить перегрузку, берущую ссылки rvalue, когда вы хотите переместить переданный аргумент. Есть несколько хороших ссылок на перемещение semantincs там, поэтому я не буду здесь его описывать.
Bonus
Чтобы помочь мне задуматься над этим простым классом probe
:
struct probe {
probe(const char* ) { std::cout << "ctr " << std::endl; }
probe(const probe& ) { std::cout << "copy" << std::endl; }
probe(probe&& ) { std::cout << "move" << std::endl; }
};
Теперь рассмотрим эту функцию:
void f(const probe& p) {
probe q(p);
// use q;
}
Вызов f("foo");
вызывает следующий вывод:
ctr
copy
Никаких сюрпризов здесь: мы создаем временный probe
, передающий const char*
"foo"
. Отсюда первая выходная линия. Затем это временное значение привязано к p
, а внутри f
создается копия q
of p
. Следовательно, вторая выходная линия.
Теперь рассмотрим выбор p
по значению, т.е. изменим f
на:
void f(probe p) {
// use p;
}
Вывод f("foo");
теперь
ctr
Некоторые удивятся, что в этом случае нет копии! В общем случае, если вы принимаете аргумент по ссылке и копируете его внутри своей функции, тогда лучше принять аргумент по значению. В этом случае вместо создания временного и копирования его компилятор может построить аргумент (p
в этом случае) непосредственно из ввода ("foo"
). Для получения дополнительной информации см. Хотите скорость? Pass by Value. от Дейва Абрахама.
В этом руководстве есть два примечательных исключения: конструкторы и операторы присваивания.
Рассмотрим этот класс:
struct foo {
probe p;
foo(const probe& q) : p(q) { }
};
Конструктор принимает probe
по ссылке const, а затем копирует его в p
. В этом случае, следуя приведенному выше руководству, не приносит никакого улучшения производительности, и конструктор копирования probe
будет вызван в любом случае. Однако при принятии q
по значению может возникнуть проблема с разрешением перегрузки, аналогичная той, с которой выполняется оператор присваивания, который я сейчас рассмотрю.
Предположим, что наш класс probe
имеет метод небрасывания swap
. Тогда предлагаемая реализация его оператора присваивания (в настоящее время рассматривается в терминах С++ 03)
probe& operator =(const probe& other) {
probe tmp(other);
swap(tmp);
return *this;
}
Затем, согласно вышеприведенному руководству, лучше написать его вот так
probe& operator =(probe tmp) {
swap(tmp);
return *this;
}
Теперь введите С++ 11 с ссылками rvalue и переместите семантику. Вы решили добавить оператор присваивания перемещения:
probe& operator =(probe&&);
Теперь вызов оператора присваивания при временном создании создает двусмысленность, потому что обе перегрузки жизнеспособны, и ни одна из них не предпочтительнее другой. Чтобы устранить эту проблему, используйте исходную реализацию оператора присваивания (используя аргумент const).
Собственно, эта проблема не относится к конструкторам и операторам присваивания и может случиться с любой функцией. (Скорее всего, вы столкнетесь с конструкторами и операторами присваивания.) Например, вызов g("foo");
, когда g
имеет следующие две перегрузки, повышает двусмысленность:
void g(probe);
void g(probe&&);