Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Designing 2 classes having references to each other using smart pointers

I'm currently updating my code by replacing all raw pointer class members with smart pointers. The scenario I'm currently working on is the following:

Having two classes Foo and Bar which know about each other (using raw pointers):

class Bar;

class Foo {
public:b
    Foo(){
        m_bar = new Bar(this);
    }
private:
    Bar* m_bar;
};

class Bar {
public:
    Bar(Foo* foo) {
        m_foo = foo;
    }
private:
    Foo* m_foo;
};

Since Foo is the creator of "m_bar" and should hold a unique instance of it which is never shared, I thought about making member "m_bar" a unique pointer, resulting in a class Foo that looks like this:

class Foo {
public:
    Foo() {
        m_bar = std::unique_ptr<Bar>(new Bar(this));
    }
private:
    std::unique_ptr<Bar> m_bar;
};

But now I'm struggling with class Bar. My idea was to make member "m_foo" a shared pointer, resulting in:

class Bar;

class Foo : public std::enable_shared_from_this<Foo> {
public:
    Foo() {
        m_bar = std::unique_ptr<Bar>(new Bar(shared_from_this()));
    }
private:
    std::unique_ptr<Bar> m_bar;
};

class Bar {
public:
    Bar(std::shared_ptr<Foo> foo) {
        m_foo = foo;
    }
private:
    std::shared_ptr<Foo> m_foo;
};

But this will throw a "bad weak pointer" exception because (as far as I found out) you can only share the "this"-pointer (shared_from_this()) after the object has been created.

Problem: I want "this" to be shared during object creation because it's necessary for the program to run correctly and it could be forgotten if you do it via function call after the object creation.

Thanks for any help.

like image 680
Tom B. Avatar asked Nov 21 '25 05:11

Tom B.


2 Answers

I would recommend only using shared_ptr when you actually want shared ownership semantics, and only using unique_ptr when you want unique ownership semantics. Otherwise you will confuse not only yourself, but others reading your code.

Your text description indicates that Foo uniquely owns each Bar, so Foo should have a unique_ptr<Bar>.

However, a Bar does not own a Foo in any sense, so Bar should neither have a unique_ptr<Foo> nor a shared_ptr<Foo>.


You could retain Bar having a Foo *. There's no need to overcomplicate things; if you are adopting a convention in your code that pointers with ownership semantics use smart pointer classes, then it follows that raw pointers have no ownership semantics.

There is a proposal observer_ptr<Foo> which is just a wrapper for raw pointer but supposed to self-document that it really doesn't have ownership semantics.

Another thing to be consider would be for Bar to have a Foo&. This decision will depend on whether you want to be able to move a Foo whilst retaining the original instance of Bar (i.e. not also moving the Bar). The reference version will not support that operation, but the pointer version would work with Foo's move-constructor modifying its Bar's back-pointer. Meaning Bar will need to be a friend of Foo since the back-pointer is private.

like image 160
M.M Avatar answered Nov 22 '25 18:11

M.M


Another option is that you use std::shared_ptr in Foo, to hold a Bar reference, and then a std::weak_ptr to hold references to Foo from Bar.

Edit: I believe what I recommend is the one closest to the spirit of how the std smart pointers are meant to be used. You should use unique_ptr / shared_ptr to delineate (and enforce) ownership, and weak_ptr when you want to hold a reference to an object in a class that does not own it. This way you can avoid using naked pointers entirely.

Using weak_ptr requires that you also use shared_ptr, since weak_ptr's cannot be created from unique_ptr's, but even if only ever one class holds a shared_ptr to the object in question, that is still in the spirit of how they are intended to be used.

Regarding M.M.'s point on ownership semantics, either shared or unique ptr signify ownership, while weak_ptr signifies a reference, so this is still fine.

Another two tips towards this architecture:

A: You could create your classes in a Factory, having their relations defined through setters, rather than each class handling the creation of its children (Dependency Injection). Ideally you could follow Alexandrescu's tips on the Factory design.

Alternatively B: You could use the named constructor idiom, with public static create(...) methods doing the actual constructing, to circumvent the error that occurs due to the class being incomplete during construction.

But I'd rather recommend alternative A here.

like image 28
onar3d Avatar answered Nov 22 '25 20:11

onar3d



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!