Использование move семантики [дубликат]

282
23 сентября 2017, 23:32

На данный вопрос уже ответили:

  • Передача в функцию по значению параметра rvalue 2 ответа

Доброго времени суток!

Решил подтянуть lvalue/rvalue ссылки и столкнулся с copy/move семантикой. Не могу понять когда вызывается move-конструкторы и операторы перемещения.

Для примера написал это:

  1. Интерфейс (Foo.h):

    // class for testing rvalue and lvalue links and copy/move constructors and operator=
    class Foo{
        private:
            int _length; // just one attribure
        public:
            Foo(); // default constructor
            Foo(const int&); // constructor from length
            ~Foo(); // destructor
            Foo(Foo&); // copy constructor
            Foo(Foo&&); // move consttructor
            Foo operator+(Foo&); // the meaning is clear from the title
            Foo& operator=(Foo&); // copy =
            Foo& operator=(Foo&&); // move =
    };
  2. Реализация (Foo.cpp):

    #include <iostream>
    #include "Foo.h"
    Foo::Foo(){
        std::cout << "\tCall Foo::Foo() -> default constructor\n";
        _length = 0;
    }
    Foo::Foo(const int& length){
        std::cout << "\tCall Foo::Foo(const int&) -> constructor\n";
        _length = length;
    }
    Foo::~Foo(){
        std::cout << "\tCall Foo::~Foo() -> destructor\n";
    }
    Foo::Foo(Foo& lvalue){
        std::cout << "\tCall Foo::Foo(Foo&) -> copy constructor\n";
        if (this != &lvalue){
            _length = lvalue._length;
        }
    }
    Foo::Foo(Foo&& rvalue){
        std::cout << "\tCall Foo:Foo(Foo&&) -> move constructor\n";
        if (this != &rvalue){
            _length = rvalue._length;
            rvalue._length = 0;
        }
    }
    Foo Foo::operator+(Foo& right){
        std::cout << "\tCall Foo+Foo\n";
        return Foo(_length + right._length);
    }
    Foo& Foo::operator=(Foo& lvalue){
        std::cout << "\tCall Foo::operator=(Foo&) -> copy =\n";
        if (this != &lvalue){
            _length = lvalue._length;
        }
        return *this;
    }
    Foo& Foo::operator=(Foo&& rvalue){
        std::cout << "\tCall Foo::operator=(Foo&) -> move =\n";
        if (this != &rvalue){
            _length = rvalue._length;
            rvalue._length = 0;
        }
        return *this;
    }

    }

  3. Непосредственно вызовы (main.cpp):

    #include <iostream>
    #include "Foo.h"
    using namespace std;
    int main(){
        int length = 7;
        cout << "a :\n";
        Foo a;
        cout << "b :\n";
        Foo b (3);
        cout << "c :\n";
        Foo c (length);
        cout << "d :\n";
        Foo d (c);
        cout << "e :\n";
        Foo e  + d);
        cout << "f :\n";
        Foo f = Foo(length);
        cout << "g :\n";
        Foo g = g + d;
        cout << endl;
    }

Ну и вот так собираю (Makefile):

bin: main.o libfoo.so
    g++ -o bin main.o -L. -lfoo -fPIC -std=c++11 -Wl,-rpath,.
main.o: main.cpp
    g++ -c main.cpp -fPIC -std=c++11
libfoo.so: Foo.o
    g++ -shared -o libfoo.so Foo.o -fPIC -std=c++11 
Foo.o: Foo.cpp
    g++ -c Foo.cpp -fPIC -std=c++11 
clear:
    rm -f *.so *.o bin
  1. Вывод:

    a :
        Call Foo::Foo() -> default constructor
    b :
        Call Foo::Foo(const int&) -> constructor
    c :
        Call Foo::Foo(const int&) -> constructor
    d :
        Call Foo::Foo(Foo&) -> copy constructor
    e :
        Call Foo+Foo
        Call Foo::Foo(const int&) -> constructor
    f :
        Call Foo::Foo(const int&) -> constructor
    g :
        Call Foo+Foo
        Call Foo::Foo(const int&) -> constructor
        Call Foo::~Foo() -> destructor
        Call Foo::~Foo() -> destructor
        Call Foo::~Foo() -> destructor
        Call Foo::~Foo() -> destructor
        Call Foo::~Foo() -> destructor
        Call Foo::~Foo() -> destructor
        Call Foo::~Foo() -> destructor

Возникает вопрос: почему не вызывается move-оператор и move-конструктор? Есть догадки по поводу того, что компилятор "оптимизирует" этот момент. Однако, точно я не уверен и хотел бы спросить совета у более подкованных в этом вопросе.

Спасибо!

Answer 1

Попробуйте такую main:

Foo make()
{
    return Foo{};
}
int main()
{
    Foo x;
    Foo f(move(x));
    Foo g;
    g = make();
}

Пояснить, почему в остальных случаях не вызываются перемещающие функции, несложно. a,b,c - думаю, очевидно. d - тоже (копируем c, а не перемещаем). В остальных случаях срабатывает оптимизация - не создается временное значение, создается сразу конечный объект. Кстати, фокус типа

Foo e (e + d);

наводит на нехорошие мысли об UB...

По-моему, так. (с) Пух

Answer 2
Foo func()
{
    Foo result;
    if (time(0) % 2)
    {
        Foo f(1);
        return f;
    } 
    return result;
}
int main(){
    // ...
    cout << "w :\n";
    Foo w(move(g));
    cout << "x :\n";
    Foo x(func());
    // ...
}

Вывод:

w :
    Call Foo:Foo(Foo&&) -> move constructor
x :
    Call Foo::Foo() -> default constructor
    Call Foo::Foo(const int&) -> constructor
    Call Foo:Foo(Foo&&) -> move constructor
    Call Foo::~Foo() -> destructor
    Call Foo::~Foo() -> destructor

Функция func() такая "хитрая" чтоб компилятор не схопнул цепочку конструкторов в "return Foo()" до одного, он имеет на это право (google: c++ Copy elision)

P.S.

Foo e (c + d);

тут нет деструктора от e + d, значит компилятор соптимизировал код так, что вызвался только конструктор для "e" от int Если оператор "+" усложнить, то компилятор не сможет так сделать. Например:

Foo Foo::operator+(Foo& right){
    std::cout << "\tCall Foo+Foo\n" ;
    Foo f(_length + right._length);
    if (time(0) % 2)
    {
        f._length += 1;
    }
    return std::move(f);
}

Вывод:

Call Foo+Foo
Call Foo::Foo(const int&) -> constructor
Call Foo:Foo(Foo&&) -> move constructor
Call Foo::~Foo() -> destructor
READ ALSO
Подскажите, как можно сделать чтобы ввод и вывод был в функции ?

Подскажите, как можно сделать чтобы ввод и вывод был в функции ?

Подскажите, как можно сделать чтобы ввод и вывод был в функции ?

241
Динамические массивы с++

Динамические массивы с++

Ребят помогите пожалуйстаВот задача

248
Правильность реализации деструктора

Правильность реализации деструктора

Написал небольшой класс который в будущем будет выступать в роли полноценного фреймворка, все работает, утечек вроде бы нет, нет никаких...

238
Есть ли простой способ в spring boot подсчитать количество запросов за временной промежуток?

Есть ли простой способ в spring boot подсчитать количество запросов за временной промежуток?

Допустим у меня есть сущность loan, у нее поле countryЯ хотел бы как-то реагировать на случай превышения некоторого числа запросов на секунду времени...

314