Date: Sun, 07 Nov 2021 18:04:50 +0000
Dear Experts,
I've recently been experimenting with coroutines for the
first time. One issue that I've had some problems with is
avoiding dangling this pointers when coroutine methods are
used. As an example I had a generator that did a depth-first
traversal of a tree yielding text (think HTML). I've
simplified it to remove the recursion for the example below.
(I'm using cppcoro's recursive_generator type, but this isn't
specific to that.)
#include <cppcoro/recursive_generator.hpp>
#include <memory>
#include <string>
using text_generator_t = cppcoro::recursive_generator<const char>;
class Element {
std::shared_ptr<std::string> p;
public:
text_generator_t text() const // danger!
{
for (auto c: *p) co_yield c;
}
friend text_generator_t text2(Element e) // safe.
{
for (auto c: *(e.p)) co_yield c;
}
};
class Document {
public:
Element root_element();
};
void f(Document doc)
{
auto text = doc.root_element().text(); // danger
//auto text = text2(doc.root_element()); // safe
std::string s{ text.begin(), text.end() };
}
Element is supposed to be a light-weight type storing a handle (a
shared_ptr above) to the underlying data, so it's reasonable for
Document to return an Element by value.
The problem is that the Element returned by doc.root_element() has
been destroyed by the time that we try to iterate the generator.
If instead we had text() as a friend function we could take the
Element by value and it would work.
This is not unlike how this is captured by lambdas, where there have
been changes so that *this can be captured by value.
We have ref-qualifiers that allow us to indicate whether a method
takes *this as an rvalue or lvalue reference, but this mechanism
doesn't allow it to be taken by value.
Some similar problems be fixed by taking a copy of *this at the start
of the method, and there are similar approaches with shared_ptrs
and enable_shared_from_this, but in a generator coroutine (initial
suspend = suspend always) there is nowhere to do that.
Comments anyone?
Regards, Phil.
I've recently been experimenting with coroutines for the
first time. One issue that I've had some problems with is
avoiding dangling this pointers when coroutine methods are
used. As an example I had a generator that did a depth-first
traversal of a tree yielding text (think HTML). I've
simplified it to remove the recursion for the example below.
(I'm using cppcoro's recursive_generator type, but this isn't
specific to that.)
#include <cppcoro/recursive_generator.hpp>
#include <memory>
#include <string>
using text_generator_t = cppcoro::recursive_generator<const char>;
class Element {
std::shared_ptr<std::string> p;
public:
text_generator_t text() const // danger!
{
for (auto c: *p) co_yield c;
}
friend text_generator_t text2(Element e) // safe.
{
for (auto c: *(e.p)) co_yield c;
}
};
class Document {
public:
Element root_element();
};
void f(Document doc)
{
auto text = doc.root_element().text(); // danger
//auto text = text2(doc.root_element()); // safe
std::string s{ text.begin(), text.end() };
}
Element is supposed to be a light-weight type storing a handle (a
shared_ptr above) to the underlying data, so it's reasonable for
Document to return an Element by value.
The problem is that the Element returned by doc.root_element() has
been destroyed by the time that we try to iterate the generator.
If instead we had text() as a friend function we could take the
Element by value and it would work.
This is not unlike how this is captured by lambdas, where there have
been changes so that *this can be captured by value.
We have ref-qualifiers that allow us to indicate whether a method
takes *this as an rvalue or lvalue reference, but this mechanism
doesn't allow it to be taken by value.
Some similar problems be fixed by taking a copy of *this at the start
of the method, and there are similar approaches with shared_ptrs
and enable_shared_from_this, but in a generator coroutine (initial
suspend = suspend always) there is nowhere to do that.
Comments anyone?
Regards, Phil.
Received on 2021-11-07 12:04:53