Inversion of control is a value-proof technique which is used to modularize a system and decouple the components from each other.
Low coupling is always an advantage: it simplifies automatic testing of the components and makes the code better conforming to single responsibility principle.
Among the ways to declare a dependency to another class (service locator, property injection calling a public method / setting a public property...), the constructor injection seems the best approach.
Though it's probably the most difficult one (at least from the listed three) to implement, it comes with significant advantages:
- all the dependencies are truly visible with constructor signature;
- cyclic dependencies don't happen because of the well-defined order of instantiation.
What are the pros / cons of the many choices C++ offers to perform the injection via constructor?
Instance copyable class
Only works in case
dependency
class is completely stateless, i.e. doesn't have any members. Practically, this rarely happens becausedependency
class may store its own dependency.Raw pointer
This works like true injection. We're required to check the passed pointer for
nullptr
value.object
class does not owndependency
class, thus it's the responsibility of calling code to make sure theobject
is destroyed before thedependency
object.In real application, it's sometimes very difficult to validate.
Reference
The reference cannot be null, so it's a bit safer in this prospective.
However this approach brings additional constraints to
object
class: it has to be non-copyable since a reference cannot be copied. You have to either manually override assignment operator and copy constructor to stop from copying or inherit it from something likeboost::noncopyable
.Like with raw pointer, the ownership constraint is in place. Calling code should provide the correct destruction order for both classes, otherwise the reference becomes invalid and application crashes with access violation.
If the dependency is a const reference:
you should pay attention to the fact that the
object
class accepts references to temporary objects:Further details:
Smart pointer
Similar to raw pointer but the ownership is controlled by smart pointer mechanism.
Still need to check for
nullptr
in the constructor body.The major advantage is the
dependency
object lifetime control: there is no need for the calling application to properly control the destruction order (but consider that you need to be very careful when designing your APIs withstd::shared_ptr
).Once the
dependency
class is no longer used it's automatically destroyed byshared_ptr
destructor.There are cases when
shared_ptr
owned objects are not destroyed (so called cyclic references). However, with constructor injection, cyclic dependencies aren't possible due to the specific well-defined order of construction.This works of course if no other injection methods are used across the application.
A smart pointer has a small overhead but it isn't a real problem in the majority of cases.
Further details: