The following program attempts to create a tree of nodes consisting of references to std::deque
elements.
#include <deque>
struct Node;
using Pool = std::deque<Node>;
struct Node
{
Node(int d, Pool& pool)
: level{d}
, l{d > 0 ? pool.emplace_back(d - 1, pool) : *this}
, r{d > 0 ? pool.emplace_back(d - 1, pool) : *this}
{
}
int level;
const Node& l;
const Node& r;
int check() const
{
if(!(&l == this))
return l.check() + 1 + r.check();
else
return 1;
}
};
int main()
{
int depth{2};
Pool pool;
Node c(depth, pool);
return c.check()==7 ? 0 : 1;
}
It creates the correct number of elements, but not all references are initialized to the emplaced elements and the level
variable is not set for levels higher than 0
.
Finally, program fails due to this
being a nullptr
when the check()
function is executed.
How can the references be correctly initialized?
The problem is that you're calling emplace_back
on the deque
from a constructor being called by emplace_back
. You are not allowed to modify a container while one of its methods is already in progress. This is prohibited by section [reentrancy] of the C++ standard (thanks to @T.C.).
Except where explicitly specified in this International Standard, it is implementation-defined which functions in the C++ standard library may be recursively reentered.
To see why this sort of thing isn't allowed, here is a more obvious example. Imagine calling std::vector::push_back()
from a copy constructor which is being invoked by std::vector
during reallocation for another push_back()
in progress. Reallocation works by allocating the new chunk of memory before freeing the old one, so you'd temporarily end up with three chunks, and at best the two new elements might end up in different "new" chunks.
Fundamentally, the problem is the combination of two things:
std::deque<Node>::emplace_back()
is being called by Node::Node(...)
std::deque<Node>::emplace_back()
is making a call to Node::Node(...)
Combining these two problems, std::deque::emplace_back()
ends up calling itself. The solution is therefore to fix one of these two things.
make()
. make()
calls itself, and calls emplace_back()
(which calls Node::Node(...)
), but Node::Node(...)
never calls emplace_back()
.Node::Node(...)
is allowed to emplace_back()
, but this time emplace_back()
is not allowed to construct Node
objects. The way I do this is that I use a deque of pointers to Node
objects. However, as written this solution is not exception safe, and I consider it much uglier than the other solutions.Node::Node(int, Pool&)
! The point is, this calls itself and emplace_back()
. But emplace_back()
calls a different constructor, namely the copy constructor, which I write manually to avoid it copying references to temporaries, and which never calls back to emplace_back()
.*this
as a sentinel value. Using pointers with nullptr
is more conventional. This solution does this (so uses pointer members like solution 1), and creates the child nodes first (like solution 2), and as a bonus the creation is totally non-invasive.#include <deque>
struct Node;
using Pool = std::deque<Node>;
struct Node
{
Node(int d)
: level{d}, l{this}, r{this}
{
}
static Node& make(int d, Pool& pool)
{
pool.emplace_back(d);
Node& result = pool.back();
if (d > 0) {
result.l = &make(d - 1, pool);
result.r = &make(d - 1, pool);
}
return result;
}
int level;
const Node* l;
const Node* r;
int check() const
{
if(l != this)
return l->check() + 1 + r->check();
else
return 1;
}
};
int main()
{
int depth{2};
Pool pool;
Node& c = Node::make(depth, pool);
return c.check()==7 ? 0 : 1;
}
Note: this changes the order of elements in the deque
: child elements are inserted first, so they are now earlier than their parents.
#include <deque>
struct Node;
using Pool = std::deque<Node>;
struct Node
{
static Node& make(int d, Pool& pool)
{
Node* l = nullptr;
Node* r = nullptr;
if (d > 0) {
l = &make(d - 1, pool);
r = &make(d - 1, pool);
}
pool.emplace_back(d, l, r);
return pool.back();
}
Node(int d, Node* l_ptr, Node* r_ptr)
: level{d}
, l{l_ptr ? *l_ptr : *this}
, r{r_ptr ? *r_ptr : *this}
{
}
int level;
const Node& l;
const Node& r;
int check() const
{
if(!(&l == this))
return l.check() + 1 + r.check();
else
return 1;
}
};
int main()
{
int depth{2};
Pool pool;
Node& c = Node::make(depth, pool);
return c.check()==7 ? 0 : 1;
}
This uses references, and avoids an auxiliary function, and stores elements in the pool in the correct order. In its current form it is NOT EXCEPTION SAFE e.g. if the call to new
for r
throws an exception (e.g. std::bad_alloc
) then l
will not be freed. This would be tricky to sort out, and ultimately I think the other solutions would be neater.
#include <deque>
#include <memory>
struct Node;
using Pool = std::deque<std::unique_ptr<const Node>>;
struct Node
{
Node(int d, Pool& pool)
: level{d}
, l{d > 0 ? *new Node(d - 1, pool) : *this}
, r{d > 0 ? *new Node(d - 1, pool) : *this}
{
if (&l != this) {
pool.emplace_back(&l);
}
if (&r != this) {
pool.emplace_back(&r);
}
}
int level;
const Node& l;
const Node& r;
int check() const
{
if(!(&l == this))
return l.check() + 1 + r.check();
else
return 1;
}
};
int main()
{
int depth{2};
Pool pool;
Node c(depth, pool);
return c.check()==7 ? 0 : 1;
}
This is arguably the simplest solution, but I would argue that it's not completely obvious that it doesn't keep any dangling references to temporary objects, unlike solution 1 and solution 2.
#include <deque>
struct Node;
using Pool = std::deque<Node>;
struct Node
{
Node(int d, Pool& pool)
: level{d}
, l{d > 0 ? pool.emplace_back(Node(d - 1, pool)) : *this}
, r{d > 0 ? pool.emplace_back(Node(d - 1, pool)) : *this}
{
}
Node(const Node& other)
: level{other.level}
, l{&other.l == &other ? *this : other.l}
, r{&other.r == &other ? *this : other.r}
{
}
int level;
const Node& l;
const Node& r;
int check() const
{
if(!(&l == this))
return l.check() + 1 + r.check();
else
return 1;
}
};
The simplest solution is if you're prepared to use pointers, construct child nodes first, and use nullptr
instead of this
as a sentinel value. Note that Node
is now cleanly separated from Pool
and make()
.
#include <deque>
struct Node {
int level;
const Node* const l;
const Node* const r;
Node(int level, const Node* l, const Node* r):
level(level), l(l), r(r)
{
}
int check() const
{
if(l)
return l->check() + 1 + r->check();
else
return 1;
}
};
using Pool = std::deque<Node>;
Node* make(int d, Pool& pool)
{
Node* l = d > 0 ? make(d - 1, pool) : nullptr;
Node* r = d > 0 ? make(d - 1, pool) : nullptr;
return &pool.emplace_back(d, l, r);
}
A solution inspired from solutions 2 and 4 from Arthur Tacca answer (if you like it, give a +1 to Arthur; if you don't like it: it's my fault).
This solution use a second constructor (as solution 4) but call it directly (as delegating constructor) an a static method (inspired by solution 2) used to create the childs of the new node.
Remain unaltered the main point from Arthur: it's necessary to avoid a emplace_back()
that call itself and insert the child nodes before the father.
Works also with c++11
#include <deque>
struct Node;
using Pool = std::deque<Node>;
struct Node
{
Node (int d, Pool & pool, Node const * pl, Node const * pr)
: level { d }, l { pl ? *pl : *this }, r { pr ? *pr : *this }
{ }
static Node const * getChild (int d, Pool & pool)
{
Node const * ret { nullptr };
if ( d-- > 0 )
{
pool.emplace_back(d, pool, getChild(d, pool), getChild(d, pool));
ret = & pool.back();
}
return ret;
}
Node (int d, Pool& pool)
: Node { d, pool, getChild(d, pool), getChild(d, pool) }
{ }
int level;
Node const & l;
Node const & r;
int check() const
{ return (!(&l == this)) ? l.check() + 1 + r.check() : 1 ; }
};
int main ()
{
int depth{2};
Pool pool;
Node c(depth, pool);
return c.check()==7 ? 0 : 1;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With