I have been looking at some mature C++ projects and I have noticed a pattern where preprocessor flags are used to enable features at compile-time.
For example:
#ifdef MY_WIDGET
Widget createMyWidget() {
// etc...
}
#endif
Then elsewhere in the code:
#ifdef MY_WIDGET
widgets.push_back(createMyWidget());
// etc...
#endif
To me this seems unnecessary since we can employ a strategy pattern, either using inheritance or std::function.
Currently the user's application might look like this (where the library has been compiled with -DMY_WIDGET):
#include <library/startApp.hpp>
int main() {
startApp(); // createMyWidget will be called by the library
return 0;
}
But instead, we could redesign the library so that the user can write this:
#include <library/startApp.hpp>
#include <my-widget-plugin/createMyWidget.hpp>
int main() {
const std::vector<Widget> widgets = { createMyWidget() };
startApp(widgets);
return 0;
}
Now the library does not have any compilation switches, which makes the build much simpler, but we can still extend the functionality of the library. This also prevents potential confusion in the case where someone compiles a library without a feature but then tries to use that feature mistakenly.
Appropriate use of const allows the compiled binary to be as efficient in either case. If the widgets should be instantiated lazily, we can pass a vector of factories.
Are preprocessor-based feature switches simply a less controlled version of the strategy pattern?
Aucun commentaire:
Enregistrer un commentaire