LINUX.ORG.RU

История изменений

Исправление 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]
}