проблема с move семантикой
От: Vinick Россия  
Дата: 27.08.18 21:16
Оценка:
Помогите разобраться, что происходит и что я сделал неправильно.
  Код
#include <iostream>
struct Dim;
struct Group;

struct Cross {
  int i;
  Cross(int l):i(l) { std::cout << "Cross() = " << (uint64_t)this << std::endl;}
  Dim dim();
};

struct Dim {
  Cross * ptr;
  explicit  Dim(Cross * c):ptr(c) {
    std::cout << "Dim(Cross*) = " << (uint64_t)this << " : " << (uint64_t)ptr << std::endl;
  }
  Dim(Dim && d):ptr(d.ptr) {
    std::cout << "Dim(&&) = " << (uint64_t)this << " : " << (uint64_t)ptr << std::endl;
  }
  Group group();
};

struct Group {
  Dim * ptr;
  explicit Group(Dim * c):ptr(c) {
    std::cout << "Group(Dim*) = " << (uint64_t)this << " : " << (uint64_t)ptr << std::endl;
  }
  Group(Group && d):ptr(d.ptr) {
    std::cout << "Group(&&) = " << (uint64_t)this << " : " << (uint64_t)ptr << std::endl;
  }

};

Dim Cross::dim() {
  Dim d(this);
  return d;
}

Group Dim::group() {
  Group g(this);
  return g;
}

template<typename D>
struct Op {
  D g;
  Op(Op && o):g(std::move(o.g)) {
    std::cout << "Op(&&) = " << (uint64_t)this << " : " << (uint64_t)g.ptr << std::endl;
  }
  Op(D && gg):g(std::move(gg)) {
    std::cout << "Op(D&&) = " << (uint64_t)this << " : " << (uint64_t)g.ptr << std::endl;
  }
};

template<typename D>
struct Cons {
  D op;
  Cons(Cons && c):op(std::move(c.op)) {
    std::cout << "Cons(&&) = " << (uint64_t)this << " : " << (uint64_t)op.g.ptr << std::endl;
  }
  explicit  Cons(D && gg):op(std::move(gg)) {
    std::cout << "Cons(Op&&) = " << (uint64_t)this << " : " << (uint64_t)op.g.ptr << std::endl;
  }
};

template<typename D>
decltype(auto) make_consumer1(D &&d) {
  auto op = Op<decltype(d)>(std::move(d));
  return Cons<decltype(op)>(std::move(op));
}
template<typename G>
decltype(auto) make_consumer2(G && g) { // <---------если здесь передавать аргумент по значению, то проблема исчезает.
  auto gop = Op<decltype(g)>(std::move(g));
  return Cons<decltype(gop)>(std::move(gop));
}


int main() {
  Cross c(10);
  auto cons1 = make_consumer1(c.dim());
  std::cout << "cons1 "<< cons1.op.g.ptr->i << std::endl;
  auto cons2 = make_consumer2(cons1.op.g.group());
  std::cout << cons2.op.g.ptr->ptr->i << std::endl; // <------------ ERROR: должно напечатать 10, а печатает мусор
  return 0;
}


Код выводит мусор. Address sanitizer сообщает "stack-use-after-scope". Проблема воспроизводится только на GCC (проверял на 8.2.0 и 5.4) и только с оптимизацией -O0 или -O1,
точнее на высоких уровнях оптимизации печаетается правильный результат, но AddressSanitizer все равно ругается.
Отредактировано 27.08.2018 21:24 Vinick . Предыдущая версия .
Re: проблема с move семантикой
От: watchmaker  
Дата: 27.08.18 22:37
Оценка:
Здравствуйте, Vinick, Вы писали:

V>Помогите разобраться, что происходит и что я сделал неправильно.

Берёшь указатель на временный объект и используешь его после смерти объекта.

С movе-семантикой проблема непосредственно не связана.




V>// <---------если здесь передавать аргумент по значению, то проблема исчезает.

Не исчезает, а просто глючит по другому.
Re[2]: проблема с move семантикой
От: Vinick Россия  
Дата: 28.08.18 05:39
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Здравствуйте, Vinick, Вы писали:


V>>Помогите разобраться, что происходит и что я сделал неправильно.

W>Берёшь указатель на временный объект и используешь его после смерти объекта.

А можно пальцем ткнуть? Я хоть убей не вижу. Вроде все объекты на которые хранятся указатели, лежат на стеке в main.
Re: проблема с move семантикой
От: Igore Россия  
Дата: 28.08.18 06:23
Оценка:
Здравствуйте, Vinick, Вы писали:

V>Помогите разобраться, что происходит и что я сделал неправильно.

Добавляй Move assignment operator да и конструкторы копирования я бы тоже добавил, раз уж начал руками перемещение писать, то описывай все операторы.
Re: проблема с move семантикой
От: wander  
Дата: 28.08.18 06:39
Оценка: 35 (1) +2
Здравствуйте, Vinick, Вы писали:

V> auto op = Op<decltype(d)>(std::move(d));

V>....
V>....
V>....
V> auto gop = Op<decltype(g)>(std::move(g));
V>....

Здесь в шаблон уходит ссылка. Т.е. эти gop и op хранят ссылки, а не объекты. Естественно привязанные к ним объекты дохнут и ты получаешь проблему.
ИМХО, перестарался с автовыводом типов.

Добавление в Op и Cons std::remove_reference_t<D> решает проблему.
Отредактировано 28.08.2018 6:58 wander . Предыдущая версия . Еще …
Отредактировано 28.08.2018 6:57 wander . Предыдущая версия .
Re[3]: проблема с move семантикой
От: _niko_ Россия  
Дата: 28.08.18 06:50
Оценка:
Здравствуйте, Vinick, Вы писали:

V>А можно пальцем ткнуть? Я хоть убей не вижу. Вроде все объекты на которые хранятся указатели, лежат на стеке в main.


Добавь логирование this, например в класс Dim:

struct Dim {
    Cross * ptr;
    explicit  Dim(Cross * c) :ptr(c) {
        std::cout << "Dim(Cross*) = " << (uint64_t)this << " : " << (uint64_t)ptr << std::endl;
    }
    Dim(Dim && d) :ptr(d.ptr) {
        std::cout << "Dim(&&) = " << (uint64_t)this << " : " << (uint64_t)ptr << std::endl;
    }
    ~Dim() {
        std::cout << "~Dim() = " << (uint64_t)this << std::endl;
    }
    Group group() {
        std::cout << "Dim::group = " << (uint64_t)this << std::endl;
        Group g(this);
        return g;
    }
};


Вот output из ideone.com:
Cross() = 140727019914912
Dim(Cross*) = 140727019914960 : 140727019914912
Op(D&&) = 140727019914976 : 140727019914912
Op(&&) = 140727019914928 : 140727019914912
Cons(Op&&) = 140727019914928 : 140727019914912
~Dim() = 140727019914960
cons1 10
Dim::group = 140727019914960
Group(Dim*) = 140727019914960 : 140727019914960
Op(D&&) = 140727019914976 : 140727019914960
Op(&&) = 140727019914944 : 140727019914960
Cons(Op&&) = 140727019914944 : 140727019914960
-1878505776
Re: проблема с move семантикой
От: andyp  
Дата: 28.08.18 08:00
Оценка:
Здравствуйте, Vinick, Вы писали:

V>Код выводит мусор. Address sanitizer сообщает "stack-use-after-scope". Проблема воспроизводится только на GCC (проверял на 8.2.0 и 5.4) и только с оптимизацией -O0 или -O1,

V>точнее на высоких уровнях оптимизации печаетается правильный результат, но AddressSanitizer все равно ругается.

В Group сохраняешь указатель на дохлый Dim.
Re[2]: проблема с move семантикой
От: uzhas Ниоткуда  
Дата: 28.08.18 08:31
Оценка: +1
Здравствуйте, wander, Вы писали:

W>Здесь в шаблон уходит ссылка. Т.е. эти gop и op хранят ссылки, а не объекты. Естественно привязанные к ним объекты дохнут и ты получаешь проблему.

W>ИМХО, перестарался с автовыводом типов.

W>Добавление в Op и Cons std::remove_reference_t<D> решает проблему.


вот так подфиксил: https://ideone.com/KIioaB
кое-где добавил std::decay_t, кое-где при move сырого указателя сделал зануление в источнике (для исходной проблемы неактуально, однако так желательно делать)
Re[3]: проблема с move семантикой
От: wander  
Дата: 28.08.18 09:14
Оценка:
Здравствуйте, uzhas, Вы писали:

U>вот так подфиксил: https://ideone.com/KIioaB


Я бы немного подругому сделал (как по мне, корректность состояния Op и Cons не должны зависеть от внешних действий; то, что там внутри нужны именно объекты, мне кажется очевидным), ну да ладно
Re[2]: проблема с move семантикой
От: Кодт Россия  
Дата: 30.08.18 01:28
Оценка:
Здравствуйте, wander, Вы писали:

W>Здесь в шаблон уходит ссылка. Т.е. эти gop и op хранят ссылки, а не объекты. Естественно привязанные к ним объекты дохнут и ты получаешь проблему.

W>ИМХО, перестарался с автовыводом типов.

Имхо, наступил на грабельку синтаксиса плюсов. Где T&& в объявлении функции по-разному трактуется для нешаблонных и шаблонных аргументов.
some foo(ValueType&& xvalue);

template<class GeneralizedReferenceType>
some bar(GeneralizedReferenceType&& glvalue);

Из-за чего и приходится явно расставлять remove_reference_t (или decay_t) и forward.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.