Search code examples
c++boostdependency-injectionshared-ptr

[Boost]::DI creating unique shared_ptr objects from injector


Taking this sample code I'd like to have as a result that button1 and button2 are two separate ojects.

#include <iostream>
#include <memory>
#include "di.hpp"
namespace di = boost::di;

struct CommandQueue {
    void addCommand() {}
};

struct Control {
    Control( CommandQueue &cq ) : cq( cq ) {
        static int sid{};
        id = ++sid;
    }

    CommandQueue& cq;
    int id{};
};

int main() {

    auto injector = di::make_injector( di::bind<CommandQueue>().in(di::singleton) );

    auto button1 = injector.create<std::shared_ptr<Control>>();
    auto button2 = injector.create<std::shared_ptr<Control>>();

    std::cout << "button1->id = " << button1->id << std::endl;
    std::cout << "button2->id = " << button2->id << std::endl;

    return 0;
}

The current output is:

button1->id = 1

button2->id = 1

Instead of the intended:

button1->id = 1

button2->id = 2

Removing the di::singleton lifetime scope from CommandQueue singleton also doesn't fix it. I know the lifetime scope for a shared_ptr is a singleton by default but I thought that was referred to the injected dependency not the actual object created with create.


Solution

  • Indeed the simplest thing could be

    auto button1 = injector.create<Control>();
    auto button2 = injector.create<Control>();
    
    std::cout << "button1.id = " << button1.id() << std::endl;
    std::cout << "button2.id = " << button2.id() << std::endl;
    

    Prints

    button1.id = 1
    button2.id = 2
    

    If you must have shared-pointers, the next simplest thing would be

    auto button1 = std::make_shared<Control>(injector.create<Control>());
    auto button2 = std::make_shared<Control>(injector.create<Control>());
    
    std::cout << "button1->id = " << button1->id() << std::endl;
    std::cout << "button2->id = " << button2->id() << std::endl;
    

    Conceptually you want a control-factory, not a control. So, you should consider creating a factory from the dependency container:

    #include <boost/di.hpp>
    #include <iostream>
    #include <memory>
    namespace di = boost::di;
    
    struct CommandQueue {
        void addCommand() {}
    };
    
    struct Control {
        Control(CommandQueue &cq) : _cq(cq), _id(idgen()) { }
    
        int id() const { return _id; }
    
        struct Factory {
            Factory(CommandQueue& cq) : _cq(cq) {}
            CommandQueue& _cq;
    
            template <typename... Args>
            auto operator()(Args&&... args) const {
                return std::make_shared<Control>(_cq, std::forward<Args>(args)...);
            }
        };
      private:
        static int idgen() { static int sid{}; return ++sid; }
        CommandQueue &_cq;
        int _id{};
    };
    
    int main() {
        auto injector = di::make_injector(di::bind<CommandQueue>().in(di::singleton));
    
        auto factory = injector.create<Control::Factory>();
        auto button1 = factory();
        auto button2 = factory();
    
        std::cout << "button1->id = " << button1->id() << std::endl;
        std::cout << "button2->id = " << button2->id() << std::endl;
    }