gracicot / kangaru

🦘 A dependency injection container for C++11, C++14 and later
MIT License
494 stars 39 forks source link

Get raw or unique ptr from container.service() #85

Closed timofurrer closed 5 years ago

timofurrer commented 5 years ago

Is it possible to receive a unique_ptr or raw pointer from a service() call?

Something like this:

Foo *foo = container.service<FooService>();
std::unique_ptr<Foo> foo = container.service<FooService>();

Or is there any other way to let kangaru create the requested service on the heap?

gracicot commented 5 years ago

Yes. In fact, as for now all single services are allocated on the heap so their address stay stable.

The confusion might come from the fact that service are injected and returned by reference.

So for a single service you can use it like that:

struct Foo {};
struct FooService : kgr::single_service<Foo> {};

Foo* foo_ptr = &container.service<FooService>();
Foo& foo_ref =  container.service<FooService>();

assert(foo_ptr == &foo_ref); // passes

If on the contrary your service is not a single, they are by default injected and returned by value:

struct Foo {};
struct FooService : kgr::service<Foo> {};

Foo foo_value = container.service<FooService>();

From what I understand you desire to have raw (non owning) pointer by default for single and std::unique_ptr for non-single?

To enable std::unique_ptr on a non-single, simply use kgr::unique_service:

struct Foo {};
struct FooService : kgr::unique_service<Foo> {};

std::unique_ptr foo_ptr = container.service<FooService>();

For raw pointer on single services, it takes an extra step. You'll have to extend the library to add support for raw pointer injection:

template<typename T, typename Dependencies>
struct raw_single_service : kgr::single_service<T, Dependencies> {
    auto forward() const -> T* {
        return &this->instance();
    }
};

Then use your new service type:

struct Foo {};
struct FooService : raw_single_service<Foo> {};

// Yay! Pointer by default
Foo* foo_ptr = container.service<FooService>();

That should be it! Thanks for reporting an issue. Please tell me if this solves your problem.

timofurrer commented 5 years ago

The part with the unique_service<> was exactly what I was looking for.

Thanks for the quick help!

Is it also possible to emplace a unique_ptr as an external service? Is there something like an extern_unique_service ? Or a way to transfer ownership of a raw pointer?

gracicot commented 5 years ago

No that won't work with the current model. You see, kgr::unique_service is not a single service. Everytime you ask the container for an instance of it, it will construct a new one. So I don't see a way it could contain one and also construct a new one each time. container.emplace<...>(...) only work for single service, because it's the only kind the container need to contain.

For a unique service it cannot a single service since it would require to copy the unique pointer (or worse, return a unique pointer by reference). How would the container return the same std::unique_ptr (the one you emplaced) each time you call container.service<...>()?

Maybe you want a factory function from your code to be called by the container each time you ask for a std::unique_ptr<Foo>?

If you have an already constructed std::unique_ptr<Foo> that the container need to inject into other services as reference then you need kgr::extern_service<Foo> and set the instance with container.emplace<FooService>(foo_uptr.get()). Your current system stay the owner of the unique pointer, and the container simply use a raw pointer to it.

If you need the same thing but with services injected as a raw pointer that comes from that unique_ptr, then you will need a similar adaptation as my other comment:

template<typename T>
struct extern_rawptr_service : kgr::extern_service<T> {
    auto forward() const -> T* {
        return &this->instance();
    }
};
timofurrer commented 5 years ago

Thanks!

I've now used the extern_rawptr_service you've provided above and declared a service like this:

struct FooService : extern_rawptr_service<IBarTpl<Bar>> {};

Now I struggle to supply the instance. What I've tried:

IBarTpl<Bar> *b = ...;
container.emplace<FooService>(b);

The error seem to be somewhere at:

/usr/include/kangaru/container.hpp:521:3: error: no matching function for call to ‘kgr::container::make_contained_service(IBarTpl<Bar> *)’

Any ideas what I'm doing wrong here? Do I need to provide another helper in my extern_rawptr_service definition?

timofurrer commented 5 years ago

I could solve it by emplacing with a reference instead of the pointer: container.emplace<FooService>(*b).

gracicot commented 5 years ago

Ah yes true, it will need a reference. I forgot that detail. This is because we defined extern_rawptr_service as extending kgr::extern_service which contains a reference.

This could technically be solved by using that instead:

template<typename Type>
struct extern_rawptr_service : kgr::single_service<Type*>, kgr::supplied {};

And that is better since it's simpler.

Here's a live example.

Anyways, I'm glad my help has been useful to you. If you need anything else, please open a new issue!