This is the approach I use too and it has worked well for a project with a heavy mix of both C++ and BP.
Plan your class hierarchy ahead. Any time you create a new blueprint ask yourself whether you’ll need to be accessing a part of it from C++ (if your project uses both C++ and BP, chances are the answer is always yes). Start with a base class in C++ like Heoki suggested with all base properties and functions in place. If necessary use BlueprintNativeEvent instead of BlueprintImplementableEvent as well - there are cases where only some BPs will need to override(/implement) the function behavior so having a base implementation in C++ with BlueprintNativeEvent may be useful.
The most complex form of this I’ve seen is when you start with interfaces defined in C++ that first need to be implemented across a hierarchy of C++ classes and then finally in a child Blueprint at the end of the hierarchy which may or may not want to override the function’s behavior. For this situation there are some pitfalls and syntactical issues you’ll run into but there are some good wiki pages already out there that cover this situation in detail.