How to correctly inject and share an object to multiple other objects

Started by
7 comments, last by sun1 9 years, 2 months ago

Hi, so I have a simple question in terms of dependency injection with c++. Say I have 3 classes A, B and C. If both B and C have a dependency on A, what is the best way to inject and share A with both B and C. I could have the following (pseudo code):


B(A& a) : uniqueAptr(&a){}

C(A& a) : uniqueAptr(&a){}

Where uniqueAptr is a unique_ptr (std::unique_ptr<A> uniqueAptr) member variable and then intialize B and C like so:


A aObject;
B bObject(aObject);
C cObject(aObject);

But wouldn't that cause both bObject and cObject object to delete the pointer to aObject effectively trying to delete aObject twice (which would cause an error). Would a shared_ptr make most sense in this case? I've read a few resources online saying that shared_ptr is not all that great to use for exampple http://programmers.stackexchange.com/questions/133302/stdshared-ptr-as-a-last-resort . So what's the preferred way of doing something like the above? This is a simple example, but in reality you would have other pointer member variable and so on.

Advertisement

Yes, you should definitely not give the same pointer to two different unique_ptrs as both will try to delete what they're owning when they cease living themselves. Looks to me more like you just want to store simple pointers to A within B and C. Of course then the question is "what happens with the references when A dies?", but that's mostly a different problem (for example, making sure that the references stay valid/are updated when A dies or that A lives at least as long as both B and C).

Whether shared_ptr makes sense or not depends on what your A actually is.

Semantically a shared pointer means that the actual ownership of the underlying object is shared between multiple places. This means that it's legal and intentional program behavior when the place where the original object was created loses track of its reference to the shared_ptr (for example, if that place was itself an object and is destroyed) while the object pointed to by shared_ptr might live on and get used in multiple other places.

This is not the case for the vast majority of objects you have in your average program, and even when it happens there are often better solutions to using shared ownership (e.g. with system resources like file or texture handles shared_ptr probably pops up often in code around the world).

Note that it's perfectly legitimate to store a raw pointer here to represent the concept of "NO ownership."

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Who owns the A? What is it's lifetime?

In your simple scenario, the A is created and is guaranteed to exist for the lifetime of the B and C objects.

So B and C just need a reference (possibly const, depending on whether they will modify the A)


class B
{
private:
  /*const?*/ A& a_;

public:
  B(/*const?*/ A& a)
  : a_(a)
  {
  }
};

If the lifetimes become more complex and A is not guaranteed to exist for the life of B and C, i.e.


A* pA = new A(); // or unique_ptr or shared_ptr

B b(*pA);
C c(*pA);

delete pA;

b.DoStuffWithA();

then you need to be able to determine if your A object still exists. In that case you might use a shared_ptr to create your A and B and C would have weak_ptrs, but you would only do that if there was no clearly defined owner of the A.

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

Also keep in mind that, depending on your use case, you might not want to store a reference to A at all.

It's perfectly ok to just pass A as an argument when calling a method of B or C.


Who owns the A? What is it's lifetime?

A is should live through whilst the program is running, like for example a box2d b2World object.


So B and C just need a reference (possibly const, depending on whether they will modify the A)

Aren't storing references a bad idea? http://stackoverflow.com/questions/892133/should-i-prefer-pointers-or-references-in-member-data . Again I'm only going by what I'm reading online.


Note that it's perfectly legitimate to store a raw pointer here to represent the concept of "NO ownership."

I like this approach, even though raw pointers are usually not good. I think if someone was to read my code and saw it, it would clearly convey there is no Ownership of this b2World object. If I take this approach, it would mean that I don't need to delete my a object in the destructor of both B and C. It would need to be handled separately? Something like this then:


main(){
A aObject;
B bObject(&aObject);
C cObject(&aObject);
}

Then A would be deleted when it goes out of scope along with B and C automatically.

This is gonna be a bit ironic, given that I'm posting it online, but...

Don't form your opinions based solely on what you read from the internet.


Think for yourself. You're the one with the most information about your present scenario, and as such, the most qualified to make a good decision about how to approach it. If storing a reference is a useful solution, don't be afraid to do so just because it has some caveats.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

So B and C just need a reference (possibly const, depending on whether they will modify the A)


Aren't storing references a bad idea? http://stackoverflow.com/questions/892133/should-i-prefer-pointers-or-references-in-member-data . Again I'm only going by what I'm reading online.


Let's examine the objections in the accepted answer:

you are forced to initialise the reference in each constructor's initialiser list: there's no way to factor out this initialisation into another function (until C++0x, anyway)

First, the initialiser list is the correct place to initialise member variables. Second, it's 2015 and almost every major compiler has support for delegating constructors.
Besides, even if you can't use delegating constructors, it's not a massive maintenance burden to initialise your reference.

the reference cannot be rebound or be null. This can be an advantage, but if the code ever needs changing to allow rebinding or for the member to be null, all uses of the member need to change

If the object you are referring to cannot be null, then this is a good thing. If it can be null, then you shouldn't use a reference. In your case, your B class expects an A object to exist for the lifetime of the class.

unlike pointer members, references can't easily be replaced by smart pointers or iterators as refactoring might require

Again, you are expressing your intent through the language features. A reference implicitly tells you that the B class expects an A object to exist for the lifetime of the B object. If you change that intent, you should change the code to reflect that. Changing from a reference to a pointer will cause your code to fail to compile. That is A Good Thingtm. The compiler is now helping you see where your reference/pointer is used and forcing you to examine the usage of it in each context.


Horses for courses.
If the injected object can be NULL: use a pointer.
If the injected object can be swapped out for a different object: use a pointer
If the injected object is not clearly owned by anyone: use a shared_ptr to create it and a weak_ptr to reference it.

But...
if in the case like this where the injected object cannot be null and must exist for the lifetime of the client object: use a reference.


Note that it's perfectly legitimate to store a raw pointer here to represent the concept of "NO ownership."


I like this approach, even though raw pointers are usually not good. I think if someone was to read my code and saw it, it would clearly convey there is no Ownership of this b2World object. If I take this approach, it would mean that I don't need to delete my a object in the destructor of both B and C. It would need to be handled separately? Something like this then:
main(){
A aObject;
B bObject(&aObject);
C cObject(&aObject);
}
Then A would be deleted when it goes out of scope along with B and C automatically.

Note that that approach will work equally well with references.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

Thanks ChaosEngine! Some of the points you made, I had thought about as well but I wasn't too sure since c++ is pretty new to me. I think I'm much more confident with ownership and thinking about how to setup my dependencies.


Don't form your opinions based solely on what you read from the internet.

Well I guess it pays to have some critical thinking, that's why I posted those links so that someone could help me understand things much easier, Thanks!

This topic is closed to new replies.

Advertisement