Dependency injection in C++
Asked Answered
H

8

29

This is also a question that I asked in a comment in one of Miško Hevery's google talks that was dealing with dependency injection but it got buried in the comments.

I wonder how can the factory / builder step of wiring the dependencies together can work in C++.

I.e. we have a class A that depends on B. The builder will allocate B in the heap, pass a pointer to B in A's constructor while also allocating in the heap and return a pointer to A.

Who cleans up afterwards? Is it good to let the builder clean up after it's done? It seems to be the correct method since in the talk it says that the builder should setup objects that are expected to have the same lifetime or at least the dependencies have longer lifetime (I also have a question on that). What I mean in code:

class builder {
public:
    builder() :
        m_ClassA(NULL),m_ClassB(NULL) {
    }
    ~builder() {
        if (m_ClassB) {
            delete m_ClassB;
        }
        if (m_ClassA) {
            delete m_ClassA;
        }
    }
    ClassA *build() {
        m_ClassB = new class B;
        m_ClassA = new class A(m_ClassB);
        return m_ClassA;
    }
};

Now if there is a dependency that is expected to last longer than the lifetime of the object we are injecting it into (say ClassC is that dependency) I understand that we should change the build method to something like:

ClassA *builder::build(ClassC *classC) {
    m_ClassB = new class B;
    m_ClassA = new class A(m_ClassB, classC);
    return m_ClassA;
}

What is your preferred approach?

Homing answered 9/12, 2008 at 14:26 Comment(3)
PS you do not need to test for NULL before a delete.Ambidextrous
NB try and delete in the reverse order of creation.Ambidextrous
I know nothing about this problem space, but what occurs to me first is that the framework should own (and clean up) anything it creates or gets from factories, right ? What am I missing ? Maybe I'm getting stick in the Spring Singleton pattern ?Unsuspecting
A
14

This talk is about Java and dependency injection.

In C++ we try NOT to pass RAW pointers around. This is because a RAW pointer have no ownership semantics associated with it. If you have no ownership then we don't know who is responsible for cleaning up the object.

I find that most of the time dependency injection is done via references in C++.
In the rare cases where you must use pointers, wrap them in std::unique_ptr<> or std::shared_ptr<> depending on how you want to manage ownership.
In case you cannot use C++11 features, use std::auto_ptr<> or boost::shared_ptr<>.

I would also point out that C++ and Java styles of programming are now so divergent that applying the style of one language to the other will inevitably lead to disaster.

Ambidextrous answered 9/12, 2008 at 14:47 Comment(4)
Martin, does this mean that you inject all dependencies as references, and you copy them inside the dependent object ?Ptolemaist
@phtriver: No. It all depends on the situation. But usually we pass a reference and then inside the object we maintain that reference.Ambidextrous
breaking news 4 y after the answer: ): since couple of years ago auto_ptr is declared abomination. :D But now std has shared_ptrRealgar
breaking news 3 years later: auto_ptr was replaced by unique_ptr 4 years ago :DJolo
C
9

This is interesting, DI in C++ using templates:

http://adam.younglogic.com/?p=146

I think the author is making the right moves as to not translate Java DI into C++ too literally. Worth the read.

Cosmopolite answered 23/12, 2009 at 1:8 Comment(0)
I
6

I've recently been bitten by the DI bug. I think it solves a lot of complexity problems, especially the automated part. I've written a prototype which lets you use DI in a pretty C++ way, or at least I think so. You can take a look at the code example here: http://codepad.org/GpOujZ79

The things that are obviously missing: no scoping, no binding of interface to implementation. The latter is pretty easy to solve, the former, I've no idea.

I'd be grateful if anyone here has an opinion on the code.

Insulate answered 10/5, 2010 at 16:39 Comment(1)
I've posted the code on bitbucket now: bitbucket.org/cheez/dicpp - I still have to do provider injection but I think that is easily done. Scoping is also implemented.Insulate
S
4

Things get complicated if you don't settle on the question of ownership once and for all. You will simply have to decide in your implementation if it's possible that dependencies live longer than the objects they are injected into.

Personally I'd say no: the object into which the dependency is injected will clean up afterwards. Trying to do it through the builder means that the builder will have to live longer than both the dependency and the object into which it is injected. This causes more problems than it solves, in my opinion, because the builder does not serve any more useful purpose after the construction with the dependency injection has been completed.

Stabilizer answered 9/12, 2008 at 14:33 Comment(0)
L
3

Use RAII.

Handing a raw pointer to someone is the same as handing them ownership. If that's not what you want to do, you should give them some kind of facade that also knows how to clean up the object in question.

shared_ptr<> can do this; the second argument of its constructor can be a function object that knows how to delete the object.

Lelandleler answered 9/12, 2008 at 16:40 Comment(0)
D
2

In C++, normally, when you done things right, you don't need to write destructors at all in most cases. You should use smart pointers to delete things automatically. I think, builder don't looks like the owner of the ClassA and ClassB instances. If you don't like to use smart pointers, you should think about objects life time and their owners.

Doomsday answered 9/12, 2008 at 15:14 Comment(0)
D
2

Based on my own experience, it is best to have clear ownership rules. For small concrete objects, it is best to use direct copy to avoid cross dependency.

Sometimes cross dependency is unavoidable, and there is no clear ownership. For example, (m) A instances own (n) B instances, and certain B instances can be owned by multiple As. In this case, the best approach is to apply reference counting to B, in the way similar to COM reference counting. Any functions that take possession of B* must increase reference count first, and decrease it when releasing the possession.

I also avoid using boost::shared_ptr as it creates a new type (shared_ptr and B* become two distinct types). I found that it brings more headaches when I add methods.

Deposition answered 23/12, 2009 at 16:4 Comment(1)
Usually, when I encounter situations like your A and B example. The ownership of both A and B belongs to a higher level class, usually the creator of the A classes. In my experience there is almost always a single owner, and that is the way to avoid shared_ptr. Reimplementing a shared_ptr for yourself (ref counting) sounds like a breach of the DRY principle (do not repeat yourself), and as with all repetitions is increases the prob. of a bug occuring.Hatti
W
1

You can also check the FFEAD Dependency Injection. It provides DI on the lines of Spring for JAVA and has a non-obtrusive way of dealing with things. It also has a lot of other important features like in-built AJAX Support,Reflection,Serialization,C++ Interpreter,Business Components For C++,ORM,Messaging,Web-Services,Thread-Pools and an Application Server that supports all these features.

Whizbang answered 10/8, 2010 at 7:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.