What is Dependency Injection in C++ and Why Do You Need It?
\n\nDependency Injection (DI) is one of the key principles of object-oriented programming that allows you to make code more flexible, testable, and maintainable. If you are a beginner in C++ and have heard terms like "loose coupling" or "inversion of control" but don't fully understand them — this article is for you.
\n\nIn simple words, Dependency Injection is a technique where an object receives its dependencies from the outside, rather than creating them itself. Instead of a class creating objects of other classes on its own, these objects are "injected" into it via a constructor, setter, or interface.
\n\nThe Problem: Tight Coupling
\n\nImagine you are writing an application for sending notifications. Without DI, your code might look like this:
\n\n#include <iostream>\n#include <string>\n\nclass EmailService {\npublic:\n void send(const std::string& message) {\n std::cout << "Sending email: " << message << std::endl;\n }\n};\n\nclass Notification {\nprivate:\n EmailService emailService; // Hard dependency!\npublic:\n void notify(const std::string& msg) {\n emailService.send(msg);\n }\n};\n\nint main() {\n Notification notif;\n notif.notify("Hello, World!");\n return 0;\n}\n\nIn this example, the Notification class is tightly coupled to EmailService. If you want to add SMS notifications or logging — you will have to rewrite the Notification class. Such code is difficult to test, extend, and maintain.
The Solution: Dependency Injection
\n\nTo solve the problem of tight coupling, we use DI. The main idea is to define an interface (abstract class) for the notification service and pass it into the Notification class from the outside.
Step 1: Create an Abstract Interface
\n\n#include <iostream>\n#include <string>\n#include <memory>\n\n// Abstract interface for all notification services\nclass IMessageService {\npublic:\n virtual ~IMessageService() = default;\n virtual void send(const std::string& message) = 0;\n};\n\nStep 2: Implement Concrete Services
\n\nclass EmailService : public IMessageService {\npublic:\n void send(const std::string& message) override {\n std::cout << "📧 Email: " << message << std::endl;\n }\n};\n\nclass SMSService : public IMessageService {\npublic:\n void send(const std::string& message) override {\n std::cout << "📱 SMS: " << message << std::endl;\n }\n};\n\nStep 3: Inject the Dependency via Constructor
\n\nclass Notification {\nprivate:\n std::unique_ptr<IMessageService> service; // Dependency is injected from outside\npublic:\n // Constructor Injection\n Notification(std::unique_ptr<IMessageService> svc) \n : service(std::move(svc)) {}\n \n void notify(const std::string& msg) {\n service->send(msg);\n }\n};\n\nint main() {\n // Create a concrete implementation\n auto emailService = std::make_unique<EmailService>();\n \n // Inject it into Notification\n Notification notif(std::move(emailService));\n notif.notify("Dependency Injection works!");\n \n // Easily switch to SMS\n auto smsService = std::make_unique<SMSService>();\n Notification smsNotif(std::move(smsService));\n smsNotif.notify("Now via SMS!");\n \n return 0;\n}\n\nThree Main Ways to Inject Dependencies in C++
\n\n1. Constructor Injection
\nThe most common and recommended method. Dependencies are passed when creating the object and remain unchanged throughout its lifetime.
\n\n<