Czy to UB, aby wznowić coroutine funkcji składowej obiektu, którego żywotność się zakończyła?

9

To pytanie wynika z tego komentarza: Lambda dożywotniego wyjaśnienia dla korupcji w C ++ 20

w odniesieniu do tego przykładu:

auto foo() -> folly::coro::Task<int> {
    auto task = []() -> folly::coro::Task<int> {
        co_return 1;
    }();
    return task;
}

Pytanie brzmi więc, czy wykonanie zwróconej przez niego coroutine foospowoduje UB.

„Wywołanie” funkcji członka (po zakończeniu życia obiektu) to UB: http://eel.is/c++draft/basic.life#6.2

... każdy wskaźnik reprezentujący adres miejsca przechowywania, w którym obiekt będzie lub był zlokalizowany, może być używany, ale tylko w ograniczonym zakresie. [...] Program ma niezdefiniowane zachowanie, jeśli:

[...]

- wskaźnik jest używany do uzyskania dostępu do elementu danych niestatycznych lub wywołania funkcji elementu niestatycznego obiektu , lub

Jednak w tym przykładzie:

  • ()operator lambda nazywa się natomiast żywotność lambda jest nadal ważny
  • Następnie jest zawieszony,
  • wtedy lambda zostaje zniszczona,
  • a następnie funkcja członka (operator ()) zostanie wznowiona w pewnym momencie później.

Czy to wznowienie jest uważane za niezdefiniowane zachowanie?

Mike Lui
źródło
2
Być może poniższa odpowiedź jest odpowiednia stackoverflow.com/a/60495359/12345656 Wygląda to całkiem inaczej, ale dotyczy również funkcji składowej, w trakcie której thiswskaźnik jest unieważniany. Rozważ także dyskusję w komentarzach.
n314159

Odpowiedzi:

2

[dcl.fct.def.coroutine] p3 :

Typ obietnicy typu coroutine to std::coroutine_traits<R, P1, ..., Pn>::promise_type, gdzie Rjest typem zwracanym funkcji, i P1 ... Pnjest sekwencją typów parametrów funkcji, poprzedzoną typem parametru obiektu niejawnego (12.4.1), jeśli coroutine jest niestatyczna funkcja członka.

Niejawny parametr obiektu jest w twoim przykładzie stałym odwołaniem, a zatem odniesienie to będzie wisieć, gdy wykonanie zostanie wznowione po zniszczeniu obiektu zamknięcia.

Jednak z uwagi na to, że obiekty są niszczone podczas wykonywania funkcji składowej, jest to w gruncie rzeczy w porządku i nic innego, jak sam standard implikuje to w [podstawowym] :

Przed rozpoczęciem życia obiektu, ale po przydzieleniu pamięci, którą obiekt będzie zajmował, lub po zakończeniu życia obiektu i przed ponownym użyciem lub zwolnieniem pamięci zajmowanej przez obiekt, jakikolwiek wskaźnik reprezentujący adres Miejsce przechowywania, w którym obiekt będzie lub był zlokalizowany, może być używane, ale tylko w ograniczonym zakresie. [...]

void B::mutate() {
  new (this) D2;    // reuses storage --- ends the lifetime of *this
  f();              // undefined behavior
  ... = this;       // OK, this points to valid memory
}

(Uwaga: powyższy UB wynika z tego, że niejawne thisnie jest prane i nadal odnosi się do parametru niejawnego obiektu).

Twój przykład wydaje się być dobrze zdefiniowany, pod warunkiem, że wznowienie wykonania nie będzie podlegać tym samym regułom, co oryginalne wywołanie. Zauważ, że odwołanie do obiektu zamknięcia może być wiszące, ale nie jest dostępne w żaden sposób między zawieszeniem a wznowieniem.

Columbo
źródło
Masz na myśli „wznowienie i zakończenie” na końcu?
Davis Herring
@ DavisHerring Nie, miałem na myśli konkretnie w tych „zewnętrznych” ramach czasowych, w których nie jest jasne, czy odniesienie można przypisać do nowego odniesienia itp., Które wymagałoby prawdziwego obiektu. Fakt, że odwołanie nie jest dostępne w ukryty sposób, jest ważne, aby nie było to UB
Columbo
Ale nie wystarczy zostawić wiszące odniesienie w spokoju aż do wznowienia; musicie zostawić to w spokoju ( np. w ciele lambda) na zawsze - na resztę życia, aż do zakończenia. Może więc powinno to być „zawieszenie i zakończenie”.
Davis Herring
@ DavisHerring W szczególności wspomniałem o tym interwale, ponieważ w naszym przykładzie wiemy, że drugi jest bezpieczny.
Columbo
Pewnie; Po prostu uważam to sformułowanie za mylące. Może nikt inny tego nie robi.
Davis Herring