简体   繁体   中英

Is dependency injection useful in C++

C# uses Dependency Injection (DI) a lot to have a lossless and testable platform. For this, I need an interface and maybe a DI or Inversion of Control (IoC) container for resolving my instances.

But how do you do this in C++? I've read a little bit about this, and it seems that dependency injection in C++ isn't as big a topic as in C#. In C++ you use a reference to an object - this is the way to use DI in C++, right?

If my theory with references are correct, is there something like a container where I can resolve all the references? In C# I have a "bad class/bad project/assembly" which registers all my instances into a static container at the program start. Then, in every class, I'm able to instance the static container and can resolving a specific instance, is this possible in C++?

Are you using Dependency Injection (or whatever it is called) in C++? If yes, how you're use it? Are there similarities to C#?

For this, I need an interface and maybe a container for resolving my instances. But how you do this in C++?

In the same way. The difference is that where you "program to an interface" in C#, you "program to a base class" in C++. Additionally, you have extra tools in C++ that you do not have in C# (for example, policy-based templates implement dependency injection chosen at compilation time).

In C++ you're use a reference to an object, this is the way to use DI in C++, right?

No; this is not the way to use DI, this is a way to use DI in C++.

Also consider:

  • use a pointer to an object (or smart pointer, depending on the case)
  • use a template argument for a policy (for an example, see std::default_delete use in smart pointers)
  • use lambda calcullus with injected functors/predicates.

In C# I've a "bad class/bad project/assembly" which register all my instance into a static container at the program start.

If I understand correctly, you set all your data in this static container and use it all over the application. If this is the case, then you do not use dependency injection correctly, because this breaks Demeter's Law.

is this possible in C++?

Yes, it is perfectly possible (but you shouldn't do it, due to it breaking Demeter's law). Have a look at boost::any (this will allow you to store heterogenous objects in a container, similar to storing objects by object reference in C#).

Are you using dependency injection or whatever it is called in C++?

Yes (and it is called dependency injection :) ).

If yes, how you're use it?

As I described above (policy template arguments, injected functors and predicates as reusable components, injecting objects by reference, pointer smart pointer or value).

Using dependency injection is quite straightforward in C++. Just define an interface (a pure abstract base class) that you use as reference or pointer (or smart pointer) argument to the constructor or init function of the class you want to dependency inject into.

Then, in the unit test, inject a mock object (an instance of a class inheriting from the abstract interface class), and in real code, inject an instance of the real class (also inheriting from the same interface class).

Easy-peasy.

With C++11 as a project limit I ended up rolling my own. I loosely based it on .NET Ninject API without the Reflection ofcourse.

ServiceLocator

Note, although its called ServiceLocator (since it does not do Dependancy Injection itself) if you use lambda function bindings and preferably ServiceLocator::Module classes you get Injection (not reflection based) and it works really really well (IMO)

#include <iostream>
#include <vector>
#include "ServiceLocator.hpp"

template <class T>
using sptr = std::shared_ptr<T>;

// Some plain interfaces
class IFood {
public:
    virtual std::string name() = 0;
};

class IAnimal {
public:
    virtual void eatFavouriteFood() = 0;
};


// Concrete classes which implement our interfaces, these 2 have no dependancies
class Banana : public IFood {
public:
    std::string name() override {
        return "Banana";
    }
};

class Pizza : public IFood {
public:
    std::string name() override {
        return "Pizza";
    }
};

// Monkey requires a favourite food, note it is not dependant on ServiceLocator
class Monkey : public IAnimal {
private:
    sptr<IFood> _food;

public:
    Monkey(sptr<IFood> food) : _food(food) {
    }

    void eatFavouriteFood() override {
        std::cout << "Monkey eats " << _food->name() << "\n";
    }
};

// Human requires a favourite food, note it is not dependant on ServiceLocator
class Human : public IAnimal {
private:
    sptr<IFood> _food;

public:
    Human(sptr<IFood> food) : _food(food) {
    }

    void eatFavouriteFood() override {
        std::cout << "Human eats " << _food->name() << "\n";
    }
};

/* The SLModule classes are ServiceLocator aware, and they are also intimate with the concrete classes they bind to
   and so know what dependancies are required to create instances */
class FoodSLModule : public ServiceLocator::Module {
public:
    void load() override {
        bind<IFood>("Monkey").to<Banana>([] (SLContext_sptr slc) { 
            return new Banana();
        });
        bind<IFood>("Human").to<Pizza>([] (SLContext_sptr slc) { 
            return new Pizza();
        });
    }
};

class AnimalsSLModule : public ServiceLocator::Module {
public:
    void load() override {
        bind<IAnimal>("Human").to<Human>([] (SLContext_sptr slc) { 
            return new Human(slc->resolve<IFood>("Human"));
        });
        bind<IAnimal>("Monkey").to<Monkey>([] (SLContext_sptr slc) { 
            return new Monkey(slc->resolve<IFood>("Monkey"));
        });
    }
};

int main(int argc, const char * argv[]) {
    auto sl = ServiceLocator::create();

    sl->modules()
        .add<FoodSLModule>()
        .add<AnimalsSLModule>();

    auto slc = sl->getContext();

    std::vector<sptr<IAnimal>> animals;
    slc->resolveAll<IAnimal>(&animals);

    for(auto animal : animals) {
        animal->eatFavouriteFood();
    }

    return 0;
}

Yes, dependency injection is useful in C++ as well. There is no reason why it shouldn´t be, because it doesn´t require a specific language or syntax, but just an object-oriented class architecture (at least this is probably the most usual case).

While in C# there are only "pointers" to dynamically allocated objects, C++ has multiple variants, like "normal" local variables, multiple kind of pointers, references... additionally the concept of move semantics is very relevant to this.

In C++ you're use a reference to an object, this is the way to use DI in C++, right?

Not only. You can use whatever you want as long you can pass something to a class method and this something will exist as long as the class object does. All of the three possibilites above can do that (each of them with certain restrictions)

is there something like a container were I can resolve all this references? In C# I've a "bad class/bad project/assembly" which register all my instance into a static container

Maybe you´re missing the point of dependeny injection. It´s not the same as a bunch of "global" variables. But yes, of course this is possible in C++ too. There are classes, there is static , and that´s everything needed.

If my theory with references are correct, is there something like a container where I can resolve all the references? In C# I have a "bad class/bad project/assembly" which registers all my instances into a static container at the program start. Then, in every class, I'm able to instance the static container and can resolving a specific instance, is this possible in C++?

That's not how DI is supposed to be used, you don't pass your container to all your "consumer" class. In a well designed application you just do few resolve in the entry point and that's it. Most of the time the need for a "resolve" can be replaced by the use of a factory which will be registered then injected.

You'll have a lot of trouble testing code depending on a static class. I would recommend if you really want to inject your container in your client class to at least instance and inject it, static dependencies are hell, would be easier to mock for unit testing.

This is an old post, but i wrote a sample implementation of a di container in c++. maybe you are interested.

https://github.com/dirkwinkhaus/widic-dicontainer

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM