I appreciate these short tutorials for offering a different perspective on certain idioms or principles with direct messaging, instead of some hook or extremely fancy presentation. Although the videos require pausing (for myself) in some parts to think through them a little more, the presentation is short enough so that I don't feel trapped by having it sit in a browser tab for a while until I can commit to it. Thanks.
I believe you mentioned that this is a technique appropriate to specialisation at link-time, i.e., if you wanted to use a SPI comms interface instead of I2C for an application, that would require the source code for Gyroscope::GyroscopeImpl in Gyroscope.cpp to be physically different. Is that correct? If you wanted to decide, at run-time, whether to use SPI or I2C, would you extract an interface, say CommsDevice, implemented by I2cCommsDevice and SpiCommsDevice with a pointer to the interface held in the GyroscopeImpl class, and passed in via, e.g. its constructor (or a setter function)? (Sorry to use Java terms so much, but they are clearer than C++ - 'interface' - > 'pure virtual base class' etc 😂).
I am a bit confused because `Gyroscope.cpp` doesn't include any `GyroscopeImpl` and then you lost me about the part where a pointer is being passed via a setter or constructor. I suspect passing anything via the constructor/setter will defeat the purpose of hiding the implementation, but I could be misunderstanding you. How about this: RUclips isn't the right platform for such discussions, what if we take it on GitHub instead? Could you please open an issue in the repo (github.com/platisd/cpp-pimpl-tutorial) add any code snippets or links to files you think are necessary and then I promise you we will get to the bottom of this. :)
At the end you say you prefer not to use the pattern yourself. I think it could be enlightening to use listeners to understand why. Is it because of link-time optimization? Because it's ugly? Because boilerplate?
Great question Anthony! I didn't elaborate further on that because I wanted to keep the video mainly about pImpl and not its alternatives. I would only use the idiom if I was distributing a library and I wouldn't want (or it wouldn't be feasible) software the depends on it to have to be recompiled every time I update inner implementation of the library. In other words, if I'd go for pImpl if wanted to keep the ABI of my library stable across updates. If that wasn't the case, I would prefer some kind of polymorphism, static or runtime. I would use polymorphism instead because I find pImpl more complicated to use & understand as well as relatively less testable.
The `SpiGyroscope` example is there to show how you could still use dependency injection (DI) and have an easily testable class that contains your business logic, within `pImpl`. This example was added because `pImpl` hides dependencies, while with DI we usually let the user provide them, so it may not obvious if you'd still want to write unit tests with easily mockable dependencies. In other words, this last example is related to the rest only by it also using the `pImpl` pattern.
@@aj-uo3uh kind of, but not really. The `GyroscopeImpl` (which happens to be instantiated as a pointer) contains a `SpiGyroscope` as a member variable. Have you checked the GitHub repository in the description by the way? Perhaps it will become clearer: github.com/platisd/cpp-pimpl-tutorial/blob/master/private/src/SpiGyroscopeImpl.cpp
@@platisd Thanks, I was adding an addition at the same time of your answer. Another thing I don't understand is the incomplete type errors. I read chapter 22 of Scott Meyers c++11. I understand that when you don't write the destructor the compiler will generate it and make it inline. But client code of Gyroscope will include the .h file and see the forward declaration. Why can't the compiler, linker, not look the definition up of the impl class? Scott doesn't explain this.
Very interesting. This seems very similar to something like java Interfaces, where a consumer just knows about the interface and nothing about the implementation, right?
Well, kind of. There are JAVA-like interfaces (i.e. "pure abstract classes") in C++ as well, so those would be the direct equivalent. pImpl allows compatibility on a binary level as well, i.e. an even looser coupling. So if component "A" depends on a common interface "Foo" that is implement by "BarFoo" and everything is compiled together, then if "BarFoo" changes then "A" has to be recompiled. On the other hand, if BarFoo was hidden behind the pImpl idiom, you wouldn't have to recompile "A" or you could even switch the library out on your filesystem completely, with something else. This is the most common use case for pImpl as far as I know. You can let your user/customer depend on your library (e.g. a .so or .dll file) and if you make changes to it, you can switch it out to another one as long as you haven't changed the public interface.
That was beautifully explained. I especially love how you enunciate the key parts that often cause confusion. Thank you!
Thanks! I sometimes fear that some viewers might find the slower enunciations awkward, so it's good to hear that others like them. 😅
I2C in reality means i square c :D
Very clear explanation of pimpl. I'm adding you to my subscriptions.
I appreciate these short tutorials for offering a different perspective on certain idioms or principles with direct messaging, instead of some hook or extremely fancy presentation. Although the videos require pausing (for myself) in some parts to think through them a little more, the presentation is short enough so that I don't feel trapped by having it sit in a browser tab for a while until I can commit to it.
Thanks.
@@mashtonish thanks for the positive feedback! 😄
This is a workaround for the inefficiency and deficiency of the header inclusion mechanism. I think C++ module will make pimpl obsolete.
I haven't fully thought it through since I haven't used modules yet, but something tells me there might still be some obscure use case for it. 🤔😫
Gyro::~Gyro() = default; should also work (instead empty impl { })
It would indeed! 👍
@@platisd great video btw. keep it up! :)
I believe you mentioned that this is a technique appropriate to specialisation at link-time, i.e., if you wanted to use a SPI comms interface instead of I2C for an application, that would require the source code for Gyroscope::GyroscopeImpl in Gyroscope.cpp to be physically different. Is that correct? If you wanted to decide, at run-time, whether to use SPI or I2C, would you extract an interface, say CommsDevice, implemented by I2cCommsDevice and SpiCommsDevice with a pointer to the interface held in the GyroscopeImpl class, and passed in via, e.g. its constructor (or a setter function)? (Sorry to use Java terms so much, but they are clearer than C++ - 'interface' - > 'pure virtual base class' etc 😂).
I am a bit confused because `Gyroscope.cpp` doesn't include any `GyroscopeImpl` and then you lost me about the part where a pointer is being passed via a setter or constructor.
I suspect passing anything via the constructor/setter will defeat the purpose of hiding the implementation, but I could be misunderstanding you.
How about this: RUclips isn't the right platform for such discussions, what if we take it on GitHub instead?
Could you please open an issue in the repo (github.com/platisd/cpp-pimpl-tutorial) add any code snippets or links to files you think are necessary and then I promise you we will get to the bottom of this. :)
At the end you say you prefer not to use the pattern yourself. I think it could be enlightening to use listeners to understand why. Is it because of link-time optimization? Because it's ugly? Because boilerplate?
Great question Anthony!
I didn't elaborate further on that because I wanted to keep the video mainly about pImpl and not its alternatives.
I would only use the idiom if I was distributing a library and I wouldn't want (or it wouldn't be feasible) software the depends on it to have to be recompiled every time I update inner implementation of the library. In other words, if I'd go for pImpl if wanted to keep the ABI of my library stable across updates.
If that wasn't the case, I would prefer some kind of polymorphism, static or runtime. I would use polymorphism instead because I find pImpl more complicated to use & understand as well as relatively less testable.
Nice explanation! I really like your videos and glad that found your channel. Thanks for your work!
Thanks a lot Andriy! :)
Not clear what you do with SpiGyroscope. How is it related to the previous classes. UML would be handy.
The `SpiGyroscope` example is there to show how you could still use dependency injection (DI) and have an easily testable class that contains your business logic, within `pImpl`.
This example was added because `pImpl` hides dependencies, while with DI we usually let the user provide them, so it may not obvious if you'd still want to write unit tests with easily mockable dependencies.
In other words, this last example is related to the rest only by it also using the `pImpl` pattern.
@@platisd So do you mean that GyroscopeImpl will have a pointer to SpiGyroscope?
Oh now I see it. Indeed GyroscopeImpl contains a member of type SpiGyroscope. Never mind.
@@aj-uo3uh kind of, but not really. The `GyroscopeImpl` (which happens to be instantiated as a pointer) contains a `SpiGyroscope` as a member variable.
Have you checked the GitHub repository in the description by the way?
Perhaps it will become clearer: github.com/platisd/cpp-pimpl-tutorial/blob/master/private/src/SpiGyroscopeImpl.cpp
@@platisd Thanks, I was adding an addition at the same time of your answer. Another thing I don't understand is the incomplete type errors. I read chapter 22 of Scott Meyers c++11. I understand that when you don't write the destructor the compiler will generate it and make it inline. But client code of Gyroscope will include the .h file and see the forward declaration. Why can't the compiler, linker, not look the definition up of the impl class? Scott doesn't explain this.
Surprisingly good video!
Very interesting. This seems very similar to something like java Interfaces, where a consumer just knows about the interface and nothing about the implementation, right?
Well, kind of. There are JAVA-like interfaces (i.e. "pure abstract classes") in C++ as well, so those would be the direct equivalent. pImpl allows compatibility on a binary level as well, i.e. an even looser coupling. So if component "A" depends on a common interface "Foo" that is implement by "BarFoo" and everything is compiled together, then if "BarFoo" changes then "A" has to be recompiled.
On the other hand, if BarFoo was hidden behind the pImpl idiom, you wouldn't have to recompile "A" or you could even switch the library out on your filesystem completely, with something else. This is the most common use case for pImpl as far as I know. You can let your user/customer depend on your library (e.g. a .so or .dll file) and if you make changes to it, you can switch it out to another one as long as you haven't changed the public interface.
@@platisd Ahh so ABI stability. Thanks for explaining!
Loud and clear
Thanks for making wonderful contents
Thanks! :)
rampant acronyms, "code smells", pimpl... sometimes the things people come up with are gross.