After reading and watching much about SOLID principles I was very keen to use these principles in my work (mostly C++ development) since I do think they are good principles and that they indeed will bring much benefit to the quality of my code, readability, testability, reuse and maintainability.
But I have real hard time with the 'D' (Dependency inversion).
This principal states that:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.
Let me explain by example:
Lets say I am writing the following interface:
class SOLIDInterface {
//usual stuff with constructor, destructor, don't copy etc
public:
virtual void setSomeString(const std::string &someString) = 0;
};
(for the sake of simplicity please ignore the other things needed for a "correct interface" such as non virutal publics, private virtuals etc, its not part of the problem.)
notice, that setSomeString() is taking an std::string.
But that breaks the above principal since std::string is an implementation.
Java and C# don't have that problem since the language offers interfaces to all the complex common types such as string and containers.
C++ does not offer that.
Now, C++ does offer the possibility to write this interface in such a way that I could write an 'IString' interface that would take any implementation that will support an std::string interface using type erasure
(Very good article: http://www.artima.com/cppsource/type_erasure.html)
So the implementation could use STL (std::string) or Qt (QString), or my own string implementation or something else.
Like it should be.
But this means, that if I (and not only I but all C++ developers) want to write C++ API which obeys SOLID design principles ('D' included), I will have to implement a LOT of code to accommodate all the common non natural types.
Beyond being not realistic in terms of effort, this solution has other problems such as - what if STL changes?(for this example)
And its not really a solution since STL is not implementing IString, rather IString is abstracting STL, so even if I were to create such an interface the principal problem remains.
(I am not even getting into issues such as this adds polymorphic overhead, which for some systems, depending on size and HW requirements may not be acceptable)
So may question is:
Am I missing something here (which I guess the true answer, but what?), is there a way to use Dependency inversion in C++ without writing a whole new interface layer for the common types in a realistic way - or are we doomed to write API which is always dependent on some implementation?
Thanks for your time!
From the first few comments I received so far I think a clarification is needed: The selection of std::string was just an example. It could be QString for that matter - I just took STL since it is the standard. Its not even important that its a string type, it could be any common type.
I have selected the answer by Corristo not because he explicitly answered my question but because the extensive post (coupled with the other answers) allowed me to extract my answer from it implicitly, realizing that the discussion tends to drift from the actual question which is: Can you implement Dependency inversion in C++ when you use basic complex types like strings and containers and basically any of the STL with an effort that makes sense. (and the last part is a very important element of the question). Maybe I should have explicitly noted that I am after run-time polymorphism not compile time. The clear answer is NO, its not possible. It might have been possible if STL would have exposed abstract interfaces to their implementations (if there are indeed reasons that prevent the STL implementations to derive from these interfaces (say, performance)) then it still could have simply maintained these abstract interfaces to match the implementations).
For types that I have full control over, yes, there is no technical problem implementing the DIP. But most likely any such interface (of my own) will still use a string or a container, forcing it to use either the STL implementation or another. All the suggested solutions below are either not polymorphic in runtime, or/and are forcing quiet a some coding around the interface - when you think you have to do this for all these common types the practicality is simply not there.
If you think you know better, and you say it is possible to have what I described above then simply post the code proving it. I dare you! :-)