Should I use smart pointers in this situation?

93 views Asked by At

I know that the go-to rule is to always use shared_ptrs or other smart pointers. However, I'm not sure how exactly to implement it without copy ctor'ing with std::make_shared. I'll elaborate:

The problem

I want to create a tree-like expression, like:

struct foo{
    std::vector<foo*> v;
};

This allows me to do the following:

foo add(foo& f1, foo& f2){
    return foo {&f1, &f2};
}

That's good, because then the two will become children of a new node.

I want the following to be evaluated correctly:

foo x, y;
auto z = add(x,y);
assert(z.v[0] == &x && z.v[1] == &y);

This way, there's no copying and it's all good. However, we have a resource management issue here.

Addressing the resource management issue

If these elements are allocated on the heap, and someone unknowingly might, then we'll run into a memory leak. So the best way to do this is to use smart pointers for RAII:

struct foo{
    std::vector<std::shared_ptr<foo> > v;
};

foo add(foo& f1, foo& f2){
    return foo {std::make_shared<foo>(f1), std::make_shared<foo>(f2)};
}

This is good, but we're calling the copy ctor of f1 and f2 here. Suppose copying takes a long time and we don't want to incur that cost.

We definitely, cannot do:

foo add(foo& f1, foo& f2){
    return foo {std::shared_ptr<foo>(&f1), std::shared_ptr<foo>(&f2)};
}

because whenever we remove z in z = x+y, we will call delete on each of x and y, which could be allocated on the stack. And we definitely do not want to delete things on the stack.

So what should I do in this case?

Let me know if I should supply more information on the context.

1

There are 1 answers

8
StoryTeller - Unslander Monica On BEST ANSWER

Accept by smart pointer. Applying the notion of smart pointers correctly means you have to think about the ownership semantics of your data, and not about calling new/delete automatically.

You accept a reference to a non-const object. This does not imply sharing, only modification of the objects. And as you correctly noted, just creating smart pointers to them spells disaster.

The caller of add (even if it's just you) must know the data they pass is about to be shared. And the only way for them to know is if they pass the smart pointer themselves. So you should change add into this:

foo add(std::shared_ptr<foo> f1, std::shared_ptr<foo> f2){
    return foo{f1, f2};
}

Now, the only way a user of add can blow this up is intentionally. But they most likely won't. Furthermore, now said user has much more flexibility in calling your code. They can control the allocator and deleter for each object they pass.