Skip to content

Quick reference

poletti-marco edited this page Feb 14, 2020 · 22 revisions

This page is a quick reference for Fruit features, it's intended for people that already know the basics of Fruit. If you're just starting to learn Fruit, read the tutorial first.

Table of contents

Constructor injection

INJECT macro

The simplest way to define that a class should be injected using a constructor is to wrap the prototype of that constructor in the class definition with the INJECT macro.

class GreeterImpl : public Greeter {
private:
    Writer* writer;

public:
    INJECT(GreeterImpl(Writer* writer))
        : writer(writer) {
    }

    // ...
};

The constructor can then be defined inside the class definition (as in the snippet above) or just declared there and defined out of the class (for example, in a .cpp file if the class definition is in a header file). If the constructor is defined elsewhere, only the prototype in the class definition should be wrapped in INJECT(), the constructor definition shouldn't be.

Note that where constructor injection is desired, even 0-argument constructors must be explicitly registered, whether or not they are user-defined. Fruit never uses a constructor unless that constructor has been explicitly marked as the one that should be used for injection. This is to avoid undesired constructor injection of value types; e.g. if std::vector<SomeType> is not bound it's likely that the programmer forgot and Fruit reports an error if this binding is missing, rather than silently constructing an empty vector.

class StdoutWriter : public Writer {
public:
    INJECT(StdoutWriter()) = default;

    // ...
};

The INJECT macro can only be used when the constructor has a simple signature. It can't be used with any of the following:

  • An explicit inline specifier at the point of declaration of the constructor. In this case, the specifier could be removed since for constructors defined in the class definition it's implicit, while for constructors defined outside the class it's the inline specifier at the point of definition that counts. However if it's present it interferes with the INJECT() macro.
  • Templated constructors. In this case, you should use registerProvider instead.
  • Constructors with default values for parameters. In this case, you should use an Inject typedef or registerConstructor instead.

Note that the restriction on templates is only when the constructor itself is templated. INJECT() can be used for templated classes, as long as the constructor is not also templated.

template <typename W>
class GreeterImpl : public Greeter {
private:
    W* writer;

public:
    INJECT(GreeterImpl(W* writer))
        : writer(writer) {
    }

    // ...
};

The INJECT() macro can also be used when only some of the constructor parameters should be injected, see assisted injection for more details.

Inject typedef

When the INJECT() macro can't be used (or as an alternative to avoid using macros) an Inject typedef can be defined inside the class instead, with the constructor signature. The two examples in the INJECT section can be rewritten as:

class GreeterImpl : public Greeter {
private:
    Writer* writer;

public:
    GreeterImpl(Writer* writer)
        : writer(writer) {
    }

    typedef GreeterImpl(Writer*) Inject;
    // Or:
    // using Inject = GreeterImpl(Writer*);

    // ...
};

class StdoutWriter : public Writer {
public:
    StdoutWriter() = default; // Optional if no other constructors are defined

    typedef StdoutWriter() Inject;
    // Or:
    // using Inject = StdoutWriter();

    // ...
};

And these are two examples that couldn't be written using the INJECT() macro:

class StdoutWriter : public Writer {
public:
    StdoutWriter(const char* line_terminator = "\n");

    typedef StdoutWriter() Inject;
    // Or:
    // using Inject = StdoutWriter();

    // ...
};

class GreeterImpl : public Greeter {
private:
    Writer* writer;

public:
    template <typename W>
    GreeterImpl(W* writer)
        : writer(writer) {
    }

    template <typename W>
    GreeterImpl(std::unique_ptr<W> writer)
        : writer(writer.get()) {
    }

    typedef GreeterImpl(Writer*) Inject;
    // Or:
    // using Inject = GreeterImpl(Writer*);

    // ...
};

In the last example the first templated constructor will be used, with W=Writer. The usual overloading resolution (with template parameter deduction) is performed to determine the constructor to use for injection.

As the INJECT macro, the Inject typedef can also be used when only some of the constructor parameters should be injected, see assisted injection for more details.

registerConstructor()

While the Inject typedef doesn't have some of the limitations of the INJECT() macro, there are some situations where even the typedef is not flexible enough:

  • If you can't modify the class definition. This is typically because the class is a third-party class that is not under our control, and the third-party library doesn't use Fruit. Also, a similar situation would occur if you're trying Fruit in an existing codebase and you want to keep Fruit-specific code separate from the existing code.
  • If the class should be injected using that constructor only in some cases. For example, in other cases you want a different constructor for that class, or you want to use a provider instead of constructor injection.

The solution here is the registerConstructor() method available when constructing a component.

class GreeterImpl : public Greeter {
private:
    Writer* writer;

public:
    GreeterImpl(Writer* writer)
        : writer(writer) {
    }

    // ...
};

fruit::Component<fruit::Required<Writer>, Greeter> getGreeterComponent() {
    return fruit::createComponent()
        .registerConstructor<GreeterImpl(Writer*)>()
        .bind<Greeter, GreeterImpl>();
}

The registerConstructor() alone can be modelled as:

If only some of the constructor parameters should be injected, see the section on assisted injection.

If none of the constructors does the desired initializations, see the section on providers to see how to bind a lambda function instead.

Bindings

bind<Interface, Impl>() specifies that Interface can be injected from an object of type Impl. Impl must be a class derived from Interface. It can also bind factories, i.e. bind std::function<std::unique_ptr<Interface>(T1,...,Tn)> to std::function<std::unique_ptr<Impl>(T1,...,Tn)> or std::function<Impl(T1,...,Tn)>, for any parameter types T1,...,Tn.

See the section on factories and assisted injection to see a convenient way how to bind a std::function<std::unique_ptr<Impl>(T1,...,Tn)> in the first place.

Multibindings

Multibindings are a feature of Fruit (inspired by Guice) that allows to bind the same type multiple times, in different ways. They can be used to bind plugins, listeners, etc.

Also, since there are no constraints to the number of bindings (even 0 is allowed), they can be used for optional injection: «if T is bound, then get an instance of T and ...», however in this case T must be bound as a multibinding and not as a normal binding. See lazy injection instead if you want something like «if (...) then inject T» with T always bound but expensive to inject.

The multibindings for a given type are completely separate from the normal bindings; in most cases a type will have either multibindings or bindings, but not both.

Multibindings can depend on normal bindings (e.g. using constructor injection) but they can't depend on other multibindings.

Unlike normal bindings, multibindings do not contribute to a Component type. E.g. a component with multibindings for Foo and a binding for Bar will have type fruit::Component<Bar>, not fruit::Component<Foo, Bar>. This allows to add multibindings for any type in any component without exposing this in the component type.

Unlike normal bindings, Fruit will not attempt to remove duplicate multibindings, even if they are "the same multibinding".

Example usage:

fruit::Component<> getListenerComponent() {
    return fruit::createComponent()
        .addMultibinding<Listener, AListener>()
        .addMultibinding<Listener, SomeOtherListener>();
}

const std::vector<Listener*>& listeners = injector.getMultibindings<Listener>();
for (Listener* listener : listeners) {
    listener->notify();
}

Note that here we specify all multibindings in a single Component, but this is not necessary; as for normal bindings, Fruit will collect the multibindings in all components used to create the injector.

Providers

This section is about registering providers. For fruit::Provider<T>, see the section on lazy injection.

The simplest way to register a class for injection is using constructor injection. However, sometimes there is no constructor that does the desired initialization, and it's not possible to add one (e.g for third-party classes) or it's not reasonable to add one in that class (e.g. for value types, or if the constructor would have to do a too-complex task). In these cases, Fruit allows to construct the object using an arbitrary lambda function.

class Database {
public:
    static Database* open(std::string db_name, DbConnectionPool* pool);

    void setConnectionTimeout(int seconds);
    ...
};

fruit::Component<Database> getDatabaseComponent() {
    return fruit::createComponent()
        ... // Bind DbConnectionPool here
        .registerProvider([](DbConnectionPool* pool) {
              Database* db = Database::open("MyDb", pool);
              db->setConnectionTimeout(12);
              return db;
        });
}

If the lambda returns a pointer, as in the example above, Fruit will take ownership of that pointer and will call delete on that object when the injector is destroyed. If you don't want Fruit to take ownership, wrap the pointer in a holder struct or tuple (but note that you'll need to change the places where that type is used) or consider binding an instance.

Instead, if the lambda returns a value, Fruit will move the value into the injector and then call the class destructor when the injector is destroyed. Note that this saves a memory allocation/deallocation, so it's preferable to return a value if possible.

registerProvider() supports only lambdas with no captured variables (aka stateless lambdas). Plain functions are not supported (but you can wrap the function in a lambda).

If you would like to bind a lambda with captures or a user-defined functor, it can still be done with a workaround.

To use this user-defined functor:

struct Functor {
    Functor(int n) {...}
    MyClass operator()(Foo* foo) {...}
};

Instead of:

Component<MyClass> getMyClassComponent() {
    static const Functor aFunctor(42);
    return fruit::createComponent()
        ... // Bind Foo
        // Not allowed !!
        .registerProvider([=aFunctor](Foo* foo) { return aFunctor(foo); });
}

Write:

Component<MyClass> getMyClassComponent() {
    static const Functor aFunctor(42);
    return fruit::createComponent()
        ... // Bind Foo
        .bindInstance(aFunctor)
        .registerProvider([](Functor functor, Foo* foo) { return functor(foo); });
}

Binding instances

In most cases, it's easier and less bug-prone to let Fruit manage the lifetime of injected objects. However, in some cases it's necessary to give Fruit access to some object external to the injector, and for which Fruit has no ownership.

Binding instances is also a way to bind values that are constant during the lifetime of the injector, but vary between injectors; see the section on normalized components.

fruit::Component<Foo> getFooComponent() {
    static Foo foo = createFoo();
    return fruit::createComponent()
        .bindInstance(foo);
}

It's also possible to bind constant values, however they then need to be marked as const in the Component type too:

fruit::Component<const Foo> getFooComponent() {
    const static Foo foo = createFoo();
    return fruit::createComponent()
        .bindInstance(foo);
}

Annotated injection

There are cases when several implementation types might have the same "natural interface" they implement, except that only one of these types can be bound to it within a single injector. Annotated injection allows to have multiple bindings for the same type and to specify which binding is desired at each point of use. See the tutorial page for a complete example.

To bind a type using annotated injection, use fruit::Annotated<MarkerType, T> instead of T, where MarkerType is any type you want to use to mark this binding for T (typically MarkerType is defined as an empty struct, for simplicity).

Example binding:

class MainBrakeImpl : public Brake {
public:
    INJECT(MainBrakeImpl()) = default;
  
    // ...
};

fruit::Component<fruit::Annotated<MainBrake, Brake>> getMainBrakeComponent() {
    return fruit::createComponent()
        .bind<fruit::Annotated<MainBrake, Brake>, MainBrakeImpl>();
}

Example use:

class CarImpl : public Car {
private:
    Brake* mainBrake;
  
public:
    INJECT(CarImpl(ANNOTATED(MainBrake, Brake*) mainBrake)
        : mainBrake(mainBrake) {
    }
    // Or:
    // using Inject = CarImpl(fruit::Annotated<MainBrake, Brake*>);
    // CarImpl(Brake* mainBrake)
    //     : mainBrake(mainBrake) {
    // }
  
    // ...
};

Note that the ANNOTATED macro is only meant to be used within INJECT. Everywhere else you should write fruit::Assisted<> instead, both to define bindings that use annotated injection and to use them.

Fruit supports annotated types instead of plain types everywhere it would make sense to use them (e.g. you can use ASSISTED and ANNOTATED parameters in the same constructor, you can use an annotated type in bindInstance).

The only notable place where you should not use annotated types is to annotate the class type in a constructor:

class FooImpl : public Foo {
private:
    Bar* bar;
public:
    // Error, no need to use ANNOTATED here
    INJECT(ANNOTATED(MyAnnotation, FooImpl)(Bar* bar))
        : bar(bar) {
    }
};

When auto-injecting a class, Fruit already knows the correct annotation, so providing the annotation here would be redundant.

Another thing to note when using annotated injection is that if you bind an implementation class to an interface using 2 different annotations there will be only one instance of the implementation class:

class FooImpl : public Foo {
public:
    INJECT(Foo()) = default;
};

struct First {};
struct Second {};
Component<fruit::Annotated<First, Foo>, fruit::Annotated<Second, Foo>>
    getFooComponent() {
    
    return fruit::createComponent()
        .bind<fruit::Annotated<First, Foo>, FooImpl>()
        .bind<fruit::Annotated<Second, Foo>, FooImpl>(); // Allowed, but misleading!
}

That's because there is only one instance of every type in an injector (unless that type is annotated with multiple annotations, but FooImpl isn't). This is probably not what you want. In this case, you should also annotate the two implementation classes, so that the injector will contain a different instance for each annotation:

class FooImpl : public Foo {
public:
    INJECT(Foo()) = default;
};

struct First {};
struct Second {};
Component<fruit::Annotated<First, Foo>, fruit::Annotated<Second, Foo>>
    getFooComponent() {
    
    return fruit::createComponent()
        .bind<fruit::Annotated<First, Foo>, fruit::Annotated<First, FooImpl>>()
        .bind<fruit::Annotated<Second, Foo>, fruit::Annotated<Second, FooImpl>>();
}

Note that we did not change the definition of FooImpl, we only need to specify the annotation in the bind() calls. Fruit will then auto-inject fruit::Annotated<First, FooImpl> and fruit::Annotated<Second, FooImpl>, both using FooImpl's constructor.

Factories and assisted injection

There are cases when within a single injector, multiple instances of a given class need to be created, either by repeatedly calling a single factory function (e.g. for threads in a thread pool) or because there is no single "correct" instance (different instances are required in different places). Instead of injecting a type MyClass, a std::function<std::unique_ptr<MyClass>()> or std::function<std::unique_ptr<MyClass>(T1,...,Tn)> can be injected instead.

Example factory binding:

class MyClass {
public:
    MyClass(Foo* foo, int n);
    ...
};

Component<std::function<std::unique_ptr<MyClass>(int)>> getMyClassComponent() {
    return fruit::createComponent()
        ... // Bind Foo
        .registerFactory<std::unique_ptr<MyClass>(Foo*, fruit::Assisted<int>)>(
           [](Foo* foo, int n) {
               return std::unique_ptr<MyClass>(new MyClass(foo, n));
           });
}

Example use of the binding from the injector:

std::function<std::unique_ptr<MyClass>(int)> myClassFactory(injector);
std::unique_ptr<MyClass> x = myClassFactory(15);

Example use of the binding to inject another class:

class Bar {
public:
    INJECT(Bar(std::function<std::unique_ptr<MyClass>(int)> myClassFactory)) {
        std::unique_ptr<MyClass> x = myClassFactory(15);
        ...
    }
};

Note the fruit::Assisted<> wrapping the type int where registerFactory() is called. This allows to distinguish types that should be parameters of the factory ("assisted injection", like int in this example) from ones that should be injected by Fruit (normal, non-assisted injection, like Foo in this example).

While Fruit will only create one instance of the factory in each injector, every call to the factory potentially returns another value; Fruit does not memoize the return value. So two calls to myClassFactory(15) will return different instances.

If memoization is desired, it can be performed in the lambda passed to registerFactory().

Also, for factories, Fruit does not take ownership of the values created by the factory. So it's preferable to return a std::unique_ptr in the lambda instead of a plain pointer to give ownership of the object to the caller of the factory.

It's possible to bind factories using bind<>, see the section on bind for more info.

In simple cases, where the lambda passed to registerFactory() just calls std::unique_ptr<MyClass>(new MyClass(...)) (as in the example above) the INJECT() macro or an Inject typedef can be used in the created class itself, instead of writing the registerFactory() call explicitly in the component definition.

class MyClass {
public:
    INJECT(MyClass(Foo* foo, ASSISTED(int) n));
    ...
};

Note that when using INJECT() for assisted injection, the assisted types are wrapped with the ASSISTED() macro instead of fruit::Assisted<>.

class MyClass {
public:
    MyClass(Foo* foo, int n);

    using Inject = MyClass(Foo*, fruit::Assisted<int>);
    ...
};

Instead, if an Inject typedef is used, the types that require assisted injection should be wrapped in fruit::Assisted<> (as in the original registerFactory() call) instead of ASSISTED().

In both cases, the constructor definition (in case the constructor is defined outside the class definition) does not need to wrap the types in any way; this is obvious when using the Inject typedef, but less obvious when using the ASSISTED() macro.

If you would like to inject a factory but do not need to supply any additional assisted arguments, you can create a component that supplies a std::function<std::unique_ptr<MyClass>()> by just binding the implementation, for example:

using MyClassFactory = std::function<std::unique_ptr<MyClass>();

fruit::Component<MyClassFactory> getMyClassFactoryComponent() {
  return fruit::createComponent().bind<MyClass, MyClassImpl>();
}

Type of a component, components with requirements and automatic bindings

When using Fruit, every component has a type that declares what type bindings the component exposes and which ones (if any) the component requires.

For example, if a component has type fruit::Component<fruit::Required<T1, T2, T3>, U1, U2>, that component exposes the types U1 and U2 but requires bindings for T1, T2 and T3. In the common case where a component has no requirements, the fruit::Required<...> part can be omitted, so we would have just fruit::Component<U1, U2> in this case.

When combining components, requirements can be satisfied with a matching binding, or by installing a component that provides those types.

For example:

fruit::Component<fruit::Required<T1, T2, T3>, U1, U2> getU1U2Component();
fruit::Component<fruit::Required<T2>, T1> getT1Component();

class SomeType : public T2 {
public:
    INJECT(SomeType(T3*)) {...}
};

fruit::Component<fruit::Required<T3>, U1> getU1Component() {
    return fruit::createComponent()
        .install(getU1U2Component) // Now U1,U2 are provided, T1,T2,T3 are required
        .install(getT1Component) // Now U1,U2,T1 are provided, T2,T3 are required
        .bind<T2, SomeType>(); // Now U1,U2,T1,T2 are provided, 
                               // T3,SomeType are required
}

When Fruit compares the provides/requires with the return type of the getT1Component() function, it realizes that all types declared to be provided are already provided, but the type SomeType is currently required even though it's not declared as required in the resulting Component. At this point Fruit looks for an Inject typedef in SomeType (the INJECT() macro is just a shorthand for an Inject typedef), finds it and adds the binding to the component (automatic binding). Note that in order to add the automatic binding Fruit needs to look at the class definition of SomeType. Instead, for other types (e.g. T3) Fruit doesn't require the definition, a forward declaration is enough to make the above function compile.

When calculating the required and provided types, Fruit also ensures that there are no dependency loops between bindings and/or components. For example, the following code produces a (compile time) dependency loop error:

fruit::Component<fruit::Required<T2>, T1> getT1Component();
fruit::Component<fruit::Required<T1>, T2> getT2Component();

fruit::Component<T1, T2> getT1T2Component() {
    return fruit::createComponent()
        .install(getT1Component)
        .install(getT2Component); // Error, dependency loop.
}

Injector

A Fruit injector is an object of type fruit::Injector<T1,...,Tn>. Unlike components, injectors can't have requirements. The types T1,...Tn are the types exposed by the injector, i.e. the types that can be used in calls to get<>. Even though the injector most likely contains bindings for other types, only the types explicitly declared in the injector type can be directly obtained by the injector itself. Other types will be injected as needed using the bindings in the injector, but behind the scenes.

Multibindings behave differently, see the section on multibindings for more information.

For each type T exposed by the injector, the following get() calls are allowed:

  • injector.get<T>()
  • injector.get<T*>()
  • injector.get<T&>()
  • injector.get<const T*>()
  • injector.get<const T&>()
  • injector.get<std::shared_ptr<T>>()
  • injector.get<fruit::Provider<T>>()
  • injector.get<fruit::Provider<const T>>()

See the section on lazy injection to see what fruit::Provider is; the other types should be clear.

For constant bindings (e.g. the T in fruit::Injector<const T>) only the following get() calls are allowed:

  • injector.get<T>()
  • injector.get<const T*>()
  • injector.get<const T&>()
  • injector.get<fruit::Provider<const T>>()

Note that we don't mention any std::function here. This is because the injector treats std::function as any other type.

If T is std::function<std::unique_ptr<X>(T1,...Tn)> the above get calls are allowed as for any other T. Note that those are allowed for the std::function type, but not for X itself.

For convenience, the injector also has a conversion operator to all types that can be retrieved from it using get(). For example, this get call:

MyClass* myClass = injector.get<MyClass*>();

can be abbreviated as:

MyClass* myClass(injector);

Normalized components

Normalized components are a feature of Fruit that allows for fast creation of multiple injectors that share most (or all) the bindings.

This is an advanced feature of Fruit that allows to reduce injection time in some cases; if you're just starting to use Fruit you might want to ignore this for now (just construct an Injector from your root Component function).

Using a NormalizedComponent only helps if:

  • You create multiple injectors during the lifetime of a process. E.g. if you only create one injector at startup you won't benefit from using NormalizedComponent.

  • Some of those injectors share all (or almost all) their bindings.

When both of those requirements apply, you can switch to using NormalizedComponent in the "similar" injectors by first refactoring the injectors' root components to be of the form:

fruit::Component<...> getRootComponent(...) {
  return fruit::createComponent()
      // This contains the bindings common to the group of similar injectors.
      .install(getSharedComponent, ...)
      // This contains the bindings specific to this injector.
      .install(getSpecificComponent, ...);
}

Then you can change your injector construction from:

fruit::Injector<...> injector(getRootComponent, ...);

To:

fruit::NormalizedComponent<fruit::Required<...>, ...> normalized_component(getSharedComponent, ...);
fruit::Injector<...> injector(normalized_component, getSpecificComponent, ...);

This splits the work of constructing the Injector in two phases: normalization (where Fruit will call the Component functions to collect all the bindings and check for some classes of runtime errors) and the actual creation of the injector, during which Fruit will also collect/check the additional bindings specific to that injector.

Then you can share the same normalized_component object across all those injectors (also in different threads, NormalizedComponent is thread-safe), so that the normalization step only occurs once (i.e., you should only construct NormalizedComponent from getSharedComponent once, otherwise you'd pay the normalization cost multiple times).

Creating an Injector from a NormalizedComponent and injecting separate instances is very cheap, on the order of 2 us for an injection graph with 100 interfaces, 100 implementation classes and 900 edges (for more details see the Benchmarks page).

This might (depending of course on your performance requirements) allow you to create injectors where it would otherwise be unthinkable, e.g. creating a separate injector for each request in a server.

Injectors that share the same NormalizedComponent are still independent; for example, if you call injector.get<Foo>() in two injectors, each injector will construct its own instance of Foo.

Example usage:

fruit::Component<fruit::Required<Y>, X> getXComponent(); // Lots of bindings here

int main() {
    fruit::NormalizedComponent<fruit::Required<Y>, X> normalizedComponent(
        getXComponent());

    for (...) {
        fruit::Component<Y> yComponent = ...; // Few injector-specific bindings

        Injector<X> injector(normalizedComponent, yComponent);
        X* x = injector.get<X*>();
        ...
    }
}

Constructing an injector from a NormalizedComponent is much faster than constructing it from a Component, because most of the processing (aka normalization) has been done in advance when the Component object was converted to NormalizedComponent. It's also possible to specify some additional bindings at the point where the injector is created; however, this should be limited to very few bindings (<5 is best, <10 is still ok). If too many of these bindings are added, the injector construction can become even slower than it would have been without using the normalized component.

As mentioned before, normalized components are best suited if the vast majority of the bindings are common. A typical use case is in a server, where a separate injector is created for every request, but the bindings of the handler classes are common to all requests; in that case a normalized component can be used, and the request itself (and possibly some metadata about the request) can be added to each injector at the point of construction. See the section on injection scopes for a more detailed example.

Injection scopes

This section is meant for readers familiar with Guice. If you don't know what injection scopes are, feel free to skip this section.

Unlike Guice, Fruit does not have injection scopes. However, Fruit's normalized components can be used in situations where with Guice you would have used injection scopes.

As an example, let's consider a situation in which there are 3 scopes, with the types X, Y and Z part of the three scopes respectively:

// Outer scope
class X {
public:
    INJECT(X()) = default;
    ...
};

fruit::Component<X> getXComponent() {
    return fruit::createComponent();
}

// Middle scope
class Y {
public:
    INJECT(Y(X* x));
    ...
};

fruit::Component<fruit::Required<X>, Y> getYComponent() {
    return fruit::createComponent();
}

// Inner scope
class Z {
public:
    INJECT(Z(X* x, Y* y));
    ...
};

fruit::Component<fruit::Required<X, Y>, Z> getZComponent() {
    return fruit::createComponent();
}

Then the code that creates the injectors can be written as:

fruit::Component<> getEmptyComponent() {
    return fruit::createComponent();
}

fruit::Component<X> getXInstanceComponent(X* x) {
    return fruit::createComponent()
        .bindInstance(*x);
}

fruit::Component<X, Y> getXYInstanceComponent(X* x, Y* y) {
    return fruit::createComponent()
        .bindInstance(*x)
        .bindInstance(*y);
}

fruit::NormalizedComponent<X> outerScopeNormalizedComponent(
    getXComponent);
fruit::NormalizedComponent<fruit::Required<X>, Y> middleScopeNormalizedComponent(
    getYComponent);
fruit::NormalizedComponent<fruit::Required<X, Y>, Z> innerScopeNormalizedComponent(
    getZComponent);

for (...) {
    // We want to enter the outer scope here.
    fruit::Injector<X> outerScopeInjector(
        outerScopeNormalizedComponent, getEmptyComponent);
    X* x = outerScopeInjector.get<X*>();
    for (...) {
        // We want to enter the middle scope here.
        fruit::Injector<Y> middleScopeInjector(
            middleScopeNormalizedComponent, getXInstanceComponent, x);
        Y* y = middleScopeInjector.get<Y*>();
        for (...) {
            // We want to enter the inner scope here.
            fruit::Injector<Z> innerScopeInjector(
                innerScopeNormalizedComponent, getXYInstanceComponent, x, y);
            Z* z = innerScopeInjector.get<Z*>();
            ...
        }
    }
}

The instance of X is owned by outerScopeInjector so it lives for a full iteration of the outer loop. The instance of Y is owned by middleScopeInjector so it lives for an iteration of the middle loop. Finally, the instance of Z is owned by innerScopeInjector so it only lives for an iteration of the inner loop.

See the section on normalized components and the section on bindInstance if the code above is not clear.

Eager injection

If you create an injector that will be used concurrently by multiple threads, you either need to synchronize the calls (using a lock) or you might want to inject everything to start with, so that all get and getMultibindings calls on the injector don't need to modify it (as the objects have already been constructed). The Injector class provides a method to do this: eagerlyInjectAll(). After calling this method, the Injector object can be shared by multiple threads with no locking.

However, this does not perform lazy injection; so if you're only injecting a Provider<Foo>, Foo will not be constructed. This means that even after calling this method, get() calls on Provider objects are still not thread-safe (unless you're sure that that class has already been injected).

Lazy injection

Lazy injection is a feature that can be used when a type Foo depends on a type Bar only for some methods, and Bar is expensive to inject. This may not necessarily be because the Bar constructor is slow, it might be expensive because it triggers the injection of many other types. In this case, Foo can depend on fruit::Provider<Bar> instead of directly depending on Bar. This delays the injection of Bar (and of the types that need to be injected in order to inject the Bar object) until the get method of the provider is called.

Let's see some example code for this situation:

class Bar {
public:
    INJECT(Bar(ReallyExpensiveClass x, SomeOtherReallyExpensiveClass y));
    ...
};

class Foo {
private:
    fruit::Provider<Bar> barProvider;

public:
    INJECT(Foo(fruit::Provider<Bar> barProvider))
        : barProvider(barProvider) {
    }
    
    void doSomething() {
        Bar* bar = barProvider.get();
        ...
    }
};

Bar, ReallyExpensiveClass and SomeOtherReallyExpensiveClass are only injected when foo->doSomething() is called, not when Foo itself is injected. This can give a sizable performance win if foo->doSomething() is not always called.

Every call to get() (within the same injector) returns the same Bar instance, and that's also the same instance that will be used to inject other classes (if there are other classes that depend on Bar).

This means that the call to get() contains a branch (basically an if (is_already_initialized) {...}) so once the Bar* has been retrieved you might want to store it in a variable or field instead of calling get() multiple times.

Templated and parametrized components

get*Component() functions are just normal functions; therefore they can be templated:

template <typename T, typename U>
class FooInterface { ... };

template <typename T>
class FooImpl : public FooInterface<T, std::vector<T>> {
public:
    INJECT(FooImpl()) = default;
    ...
};

template <typename T>
fruit::Component<FooInterface<T, std::vector<T>> getFooComponent() {
    return createComponent()
        .bind<FooInterface<T, std::vector<T>>, FooImpl<T>>();
}

And they can parametrized:

class FooInterface { ... };

class OldFooImpl : public FooInterface {
public:
    INJECT(OldFooImpl()) = default;
    ...
};

class NewFooImpl : public FooInterface {
public:
    INJECT(NewFooImpl()) = default;
    ...
};

fruit::Component<FooInterface> getFooComponent(bool use_old_impl) {
    if (use_old_impl) {
        return fruit::createComponent()
            .bind<FooInterface, OldFooImpl>();
    } else {
        return fruit::createComponent()
            .bind<FooInterface, NewFooImpl>();
    }
}

The last example also shows how to do conditional injection.

When parameterizing them, however, there are some constraints on the argument types. Specifically, they must be:

  • Copy-constructible
  • Move-constructible
  • Assignable
  • Move-assignable
  • Equality-comparable (i.e., operator== must be defined for two values of that type)
  • Hashable (i.e., std::hash must be defined for values of that type)

Note that this only applies to the arguments of the function, not the types declared by the returned Component. In the example above, this means that bool must satisfy these requirements (and it does), but FooInterface, NewFooImpl and OldFooImpl don't need to.

Installing multiple components with a pack-expansion expression

Fruit has a installComponentFunctions() that helps install multiple components created using a template pack-expansion expression.

This is a very niche feature, it's only useful within a templated get*Component function that has a variadic argument. So most users will never need/use this.

Given a templated get*Component function:

template <typename T>
fruit::Component<T> getDefaultConstructorComponent() {
    return fruit::createComponent()
        .registerConstructor<T()>();
}

We can install getDefaultConstructorComponent for multiple different Ts at once, as follows:

template <typename... Ts>
fruit::Component<Ts...> getDefaultConstructorsComponent() {
    return fruit::createComponent()
        .installComponentFunctions(
            fruit::componentFunction(getDefaultConstructorComponent<Ts>)...);
}

Note that we need to wrap each get*Component function in fruit::componentFunction. This is to allow passing arguments to the get*Component functions if desired (see the previous section for more details).

Component replacements

In some cases (notably in tests) we want to install a component but without some of the components that it installs (directly or indirectly). Component replacements allow to specify these substitutions so that you don't need to split the component or create a modified copy of the component function hierarchy.

For example, if you have these components:

fruit::Component<MyDependency> getDependencyComponent() {...}
fruit::Component<Foo> getFooComponent() {
    return fruit::createComponent()
        .install(getDependencyComponent)
        .bind<Foo, FooImpl>();
}
fruit::Component<Bar> getBarComponent() {
    return fruit::createComponent()
        .install(getFooComponent)
        .bind<Bar, BarImpl>();
}

When you test Bar, you might want to replace getDependencyComponent with a component that binds a fake MyDependency:

fruit::Component<MyDependency> getFakeDependencyComponent() {...}

To do so, you can define a component like:

fruit::Component<Bar> getBarComponentWithFakeDependency() {
    return fruit::createComponent()
        .replace(getDependencyComponent).with(getFakeDependencyComponent)
        .install(getBarComponent);
}

This component is equivalent to:

fruit::Component<Bar> getBarComponentWithFakeDependency() {
    return fruit::createComponent()
        .install(getFakeDependencyComponent)
        .bind<Foo, FooImpl>()
        .bind<Bar, BarImpl>();
}

However this way you don't need to duplicate the bindings for Foo and Bar, and you don't even need to include them in the translation unit (i.e., cc/cpp file) that defines getBarComponentWithFakeDependency(). In codebases with many layers, this can save a lot of duplication. Note that the .replace(...).with(...) must appear before installing the component to which it's applied to; e.g., in the example above note how we install getBarComponent after the replacement in getBarComponentWithFakeDependency. If you add a replacement after the replaced component has been installed, Fruit will report an error at run-time. In the example above, the replaced and replacement component functions had no arguments, however it's also possible to replace component functions with args. The arguments of the replaced and replacement component functions are independent; for example .replace(getDependencyComponentWithArgs, 15).with(myFakeComponentWithNoArgs) is allowed and it would replace all install(getDependencyComponentWithArgs, 15) calls with install(myFakeComponentWithNoArgs). The component types returned by the replaced and replacement components must be the same. For example, this is NOT allowed:

fruit::Component<MyDependency, SomethingElse> getFakeDependencyComponentWithSomethingElse() {...}
fruit::Component<Bar> getBarComponentWithFakeDependency() {
    return fruit::createComponent()
        .replace(getDependencyComponent).with(getFakeDependencyComponentWithSomethingElse) // error!
        .install(getBarComponent);
}

But replacing a replaced component is allowed:

fruit::Component<MyDependency> getOtherFakeDependencyComponent() {...}
fruit::Component<Bar> getBarComponentWithOtherFakeDependency() {
    return fruit::createComponent()
        // The two replacements can appear in any order, but they must both be before the install().
        .replace(getFakeDependencyComponent).with(getOtherFakeDependencyComponent)
        .replace(getDependencyComponent).with(getFakeDependencyComponent)
        .install(getBarComponent);
}

Of course this is a simple example, in the real world the replacements and the install would probably come from other components. And note that you can also replace components that define replacements, for example:

fruit::Component<> getFakeDependencyReplacementComponent() {
    return fruit::createComponent()
        .replace(getDependencyComponent).with(getFakeDependencyComponentWithSomethingElse);
}
fruit::Component<...> getComponent() {
    return fruit::createComponent()
        .replace(getFakeDependencyReplacementComponent).with(...)
        .install(...);
}

Replacements are only installed if the replaced component is installed, otherwise they are ignored. In the first example above, if getFooComponent didn't install getDependencyComponent, when a test creates an injector for getBarComponentWithFakeDependency it would not install getFakeDependencyComponent.

For a more complete example, see the Testing chapter of the tutorial.