История изменений
Исправление quasimoto, (текущая версия) :
Выше пример с классом string и && в конструкторе. Он же — http://stackoverflow.com/q/3106110 («It's only 10 pages on my monitor» :)). Вот ещё на почитать — https://www.artima.com/cppsource/rvalue.html, http://www.cprogramming.com/c 11/rvalue-references-and-move-semantics-in-c ..., http://stackoverflow.com/q/1116641, http://stackoverflow.com/q/5770253.
То есть принимать && это всё равно что принимать & (не const), только с той разницей, что в случае & мы можем передать только lvalue, то есть, допустим, некую переменную x связанную (слева, т.е. объявлением с конструктором) с куском стека вызывающей функции — принимающая принимает ссылку (фактически указатель) на кусок стека вызывающей функции, может этот кусок изменять, может вернуть ссылку на него обратно. Теперь, если мы передаём rvalue, например временный объект, например в случае someFunc(string1 + string2), то этот временный объект (от string1 + string2) тоже (допустим!) существует на стеке вызывающей функции, но не связан с какой-либо переменной (не l, а r, то есть просто выражение дающее временный объект), прилетит тот же указатель на кусок стека, но он виден только принимающей функции — если это конструктор копирования, то ему не нужно ничего копировать, объект уже есть, он уникально наш, можно его просто подобрать (поэтому move); в случае просто функции или метода — можно его подобрать и вернуть (обычную) ссылку на него вызывающей стороне — так мы можем этот временный объект превратить в обычное lvalue.
Это примерно про && в аргументах конструкторов, функций и методов. Ещё её можно возвращать — метод временного объекта может возвращать && на часть объекта, так что вызывающий будет подбирать (move) именно её.
Для примеров передачи и возвращения в/из обычных функций (собирать с -O0, чтобы рассмотреть адреса без лишних оптимизаций):
#include <cstdio>
#include <string>
#include <memory>
struct A {
std::string label;
int x;
void print() const {
printf("A{%s}{%d}[%p]\n", label.c_str(), x, this);
}
};
A const& f(A const& x) {
printf("f-const on ");
x.print();
return x;
}
A& f(A&& x) {
printf("f-mut on ");
x.print();
x.x += 1;
return x;
}
A&& g(A&& x) {
printf("g on ");
x.print();
x.x += 2;
return std::move(x);
}
A h(A const& x) {
printf("h on ");
x.print();
return A{"copy-tmp", x.x + 3};
}
int main() {
int stack_starts;
printf("main stack starts at %p\n", &stack_starts);
// main stack starts at 0x7fff341008ec
A lvalue{"lvalue1", 1};
lvalue.print();
// A{lvalue1}{1}[0x7fff341008d8]
f(lvalue).print();
// f-const on A{lvalue1}{1}[0x7fff341008d8]
// A{lvalue1}{1}[0x7fff341008d8]
f(std::move(lvalue)).print();
// f-mut on A{lvalue1}{1}[0x7fff341008d8]
// A{lvalue1}{2}[0x7fff341008d8]
f(A{"tmp1", 2}).print();
// f-mut on A{tmp1}{2}[0x7fff341008b0]
// A{tmp1}{3}[0x7fff341008b0]
g(std::move(lvalue)).print();
// g on A{lvalue1}{2}[0x7fff341008d8]
// A{lvalue1}{4}[0x7fff341008d8]
g(A{"tmp2", 3}).print();
// g on A{tmp2}{3}[0x7fff34100898]
// A{tmp2}{5}[0x7fff34100898]
h(lvalue).print();
// h on A{lvalue1}{4}[0x7fff341008d8]
// A{copy-tmp}{7}[0x7fff34100880]
h(A{"tmp3", 4}).print();
// h on A{tmp3}{4}[0x7fff2aa25c50]
// A{copy-tmp}{7}[0x7fff2aa25c60]
lvalue.print();
// A{lvalue1}{4}[0x7fff341008d8]
}
Исправление quasimoto, :
Выше пример с классом string и && в конструкторе. Он же — http://stackoverflow.com/q/3106110 («It's only 10 pages on my monitor» :)). Вот ещё на почитать — https://www.artima.com/cppsource/rvalue.html, http://www.cprogramming.com/c 11/rvalue-references-and-move-semantics-in-c ..., http://stackoverflow.com/q/1116641, http://stackoverflow.com/q/5770253.
То есть принимать && это всё равно что принимать & (не const), только с той разницей, что в случае & мы можем передать только lvalue, то есть, допустим, некую переменную x связанную (слева, т.е. объявлением с конструктором) с куском стека вызывающей функции — принимающая принимает ссылку (фактически указатель) на кусок стека вызывающей функции, может этот кусок изменять, может вернуть ссылку на него обратно. Теперь, если мы передаём rvalue, например временный объект, например в случае someFunc(string1 + string2), то этот временный объект (от string1 + string2) тоже (допустим!) существует на стеке вызывающей функции, но не связан с какой-либо переменной (не l, а r, то есть просто выражение дающее временный объект), прилетит тот же указатель на кусок стека, но он виден только принимающей функции — если это конструктор копирования, то ему не нужно ничего копировать, объект уже есть, он уникально наш, можно его просто подобрать (поэтому move); в случае просто функции или метода — можно его подобрать и вернуть (обычную) ссылку на него вызывающей стороне — так мы можем этот временный объект превратить в обычное lvalue.
Это примерно про && в аргументах конструкторов, функций и методов. Ещё её можно возвращать — метод временного объекта может возвращать && на часть объекта, так что вызывающий будет подбирать (move) именно её.
Для примеров передачи и возвращения в/из обычных функций (собирать с -O0, чтобы рассмотреть адреса без лишних оптимизаций):
#include <cstdio>
#include <string>
#include <memory>
struct A {
std::string label;
int x;
A const& print() const {
printf("A{%s}{%d}[%p]\n", label.c_str(), x, this);
return *this;
}
};
A const& f(A const& x) {
printf("f-const on ");
x.print();
return x;
}
A& f(A&& x) {
printf("f-mut on ");
x.print();
x.x += 1;
return x;
}
A&& g(A&& x) {
printf("g on ");
x.print();
x.x += 2;
return std::move(x);
}
A h(A const& x) {
printf("h on ");
x.print();
return A{"copy-tmp", x.x + 3};
}
int main() {
int stack_starts;
printf("main stack starts at %p\n", &stack_starts);
// main stack starts at 0x7fff341008ec
A lvalue{"lvalue1", 1};
lvalue.print();
// A{lvalue1}{1}[0x7fff341008d8]
f(lvalue).print();
// f-const on A{lvalue1}{1}[0x7fff341008d8]
// A{lvalue1}{1}[0x7fff341008d8]
f(std::move(lvalue)).print();
// f-mut on A{lvalue1}{1}[0x7fff341008d8]
// A{lvalue1}{2}[0x7fff341008d8]
f(A{"tmp1", 2}).print();
// f-mut on A{tmp1}{2}[0x7fff341008b0]
// A{tmp1}{3}[0x7fff341008b0]
g(std::move(lvalue)).print();
// g on A{lvalue1}{2}[0x7fff341008d8]
// A{lvalue1}{4}[0x7fff341008d8]
g(A{"tmp2", 3}).print();
// g on A{tmp2}{3}[0x7fff34100898]
// A{tmp2}{5}[0x7fff34100898]
h(lvalue).print();
// h on A{lvalue1}{4}[0x7fff341008d8]
// A{copy-tmp}{7}[0x7fff34100880]
h(A{"tmp3", 4}).print();
// h on A{tmp3}{4}[0x7fff2aa25c50]
// A{copy-tmp}{7}[0x7fff2aa25c60]
lvalue.print();
// A{lvalue1}{4}[0x7fff341008d8]
}
Исходная версия quasimoto, :
Выше пример с классом string и && в конструкторе. Он же — http://stackoverflow.com/q/3106110 («It's only 10 pages on my monitor» :)). Вот ещё на почитать — https://www.artima.com/cppsource/rvalue.html, http://www.cprogramming.com/c 11/rvalue-references-and-move-semantics-in-c ..., http://stackoverflow.com/q/1116641, http://stackoverflow.com/q/5770253.
То есть принимать && это всё равно что принимать & (не const), только с той разницей, что в случае & мы можем передать только lvalue, то есть, допустим, некую переменную x связанную (слева, т.е. объявлением с конструктором) с куском стека вызывающей функции — принимающая принимает ссылку (фактически указатель) на кусок стека вызывающей функции, может этот кусок изменять, может вернуть ссылку на него обратно. Теперь, если мы передаём rvalue, например временный объект, например в случае someFunc(string1 + string2), то этот временный объект (от string1 + string2) тоже (допустим!) существует на стеке вызывающей функции, но не связан с какой-либо переменной (не l, а r, то есть просто выражение дающее временный объект), прилетит тот же указатель на кусок стека, но он виден только принимающей функции — если это конструктор копирования, то ему не нужно ничего копировать, объект уже есть, он уникально наш, можно его просто подобрать (поэтому move); в случае просто функции или метода — можно его подобрать и вернуть (обычную) ссылку на него вызывающей стороне — так мы можем этот временный объект превратить в обычное lvalue.
Это примерно про && в аргументах конструкторов, функций и методов. Ещё её можно возвращать — метод временного объекта может возвращать && на часть объекта, так что вызывающий будет подбирать (move) именно её.
Для пример передачи и возвращения в/из обычных функций (собирать с -O0, чтобы рассмотреть адреса без лишних оптимизаций):
#include <cstdio>
#include <string>
#include <memory>
struct A {
std::string label;
int x;
A const& print() const {
printf("A{%s}{%d}[%p]\n", label.c_str(), x, this);
return *this;
}
};
A const& f(A const& x) {
printf("f-const on ");
x.print();
return x;
}
A& f(A&& x) {
printf("f-mut on ");
x.print();
x.x += 1;
return x;
}
A&& g(A&& x) {
printf("g on ");
x.print();
x.x += 2;
return std::move(x);
}
A h(A const& x) {
printf("h on ");
x.print();
return A{"copy-tmp", x.x + 3};
}
int main() {
int stack_starts;
printf("main stack starts at %p\n", &stack_starts);
// main stack starts at 0x7fff341008ec
A lvalue{"lvalue1", 1};
lvalue.print();
// A{lvalue1}{1}[0x7fff341008d8]
f(lvalue).print();
// f-const on A{lvalue1}{1}[0x7fff341008d8]
// A{lvalue1}{1}[0x7fff341008d8]
f(std::move(lvalue)).print();
// f-mut on A{lvalue1}{1}[0x7fff341008d8]
// A{lvalue1}{2}[0x7fff341008d8]
f(A{"tmp1", 2}).print();
// f-mut on A{tmp1}{2}[0x7fff341008b0]
// A{tmp1}{3}[0x7fff341008b0]
g(std::move(lvalue)).print();
// g on A{lvalue1}{2}[0x7fff341008d8]
// A{lvalue1}{4}[0x7fff341008d8]
g(A{"tmp2", 3}).print();
// g on A{tmp2}{3}[0x7fff34100898]
// A{tmp2}{5}[0x7fff34100898]
h(lvalue).print();
// h on A{lvalue1}{4}[0x7fff341008d8]
// A{copy-tmp}{7}[0x7fff34100880]
h(A{"tmp3", 4}).print();
// h on A{tmp3}{4}[0x7fff2aa25c50]
// A{copy-tmp}{7}[0x7fff2aa25c60]
lvalue.print();
// A{lvalue1}{4}[0x7fff341008d8]
}