I would like to write a function f
which invokes two overridden virtual methods, op_1
and op_2
, of a derived class in a particular order, without exposing those methods beyond f
and the methods' containing classes.
I can think of a few approaches which work or almost work, but I'm also new to c and would like some help choosing the "best" way in terms of conventionality, maintainability, etc:
- Make
f
a public non-virtual method of the base class (Base
) which invokes the virtual ops in order, and make the ops private.
/***** .h *****/
class Base {
virtual void op_1() const = 0;
virtual void op_2() const = 0;
public:
void f();
};
/***** .cpp *****/
void Base::f() {
op_1();
op_2();
}
- Make the ops public and expose a non-member non-friend helper to invoke them in order.
/***** .h *****/
struct Base {
virtual void op_1() const = 0;
virtual void op_2() const = 0;
};
void f(const Base& b,) {
b.op_1();
b.op_2();
}
- Leave the ops private and expose a friend function to invoke them in order.
/***** .h *****/
class Base {
virtual void op_1() const = 0;
virtual void op_2() const = 0;
friend void f(const Base& b);
};
void f() {
b.op_1();
b.op_2();
}
#1 seems bad because derived classes also have access to f
, but it's not intended or useful for them and could be misused.
#2 resolves that, but is less encapsulated because the ops are public.
#3 seems to resolve both concerns, but I'm new to C and standoffish about friending things - it seems to muddle interfaces and the guidance I've read (e.g. Effective C #23) discourages its use without clear need. Is this an appropriate case for it?
#4 ??? Probably there's a better way I can't see. Enlighten me!
App-specific context, in case you want the "Why"
I need to register some data types with a 3rd party data store on app initialization - there are 2 synchronous operations I need to perform in order:
store.data_type<MyAppDataType>()
adds a table for an app-specific data type to the store.store.data_type<MyAppDataType>().member<MemberDataType>("member_name")
yields the app-specific data type registered in step 1 and sets up one of its fields for use with the library's reflection APIs - useful for debugging in non-production builds.
Operation #2 is optional, but if performed it should occur after operation #1.
I want the various modules[1] of my application to register their own data with the store to avoid having a big messy file where all the data for the app is registered together. To do this, I am including a class in each module which extends a DataRegistrar
base class - the base forces derived classes to provide module-specific implementations for the two operations above:
struct DataRegistrar {
virtual void register_data_types(lib::store& store) const = 0;
virtual void setup_reflection(lib::store& store) const = 0;
};
Elsewhere in the app, I can have some code which runs on app startup which invokes register_data_types
and setup_reflection
, in that order, for each module. Here are the approaches which map to those described above, in the simplified question:
- (Maps to #1 above)
/***** .h *****/
class DataRegistrar {
virtual void register_data_types(lib::store& store) const = 0;
virtual void setup_reflection(lib::store& store) const = 0;
public:
void register_data(lib::store& store);
};
/***** .cpp *****/
void DataRegistrar::register_data(lib::store& store) {
register_data_types(store);
#if !PROD_BUILD
setup_reflection(store);
#endif
}
- (Maps to #2 above)
/***** .h *****/
struct DataRegistrar {
virtual void register_data_types(lib::store& store) const = 0;
virtual void setup_reflection(lib::store& store) const = 0;
};
void register_data(const DataRegistrar& module_registrar, lib::store& store) {
module_registrar.register_data_types(store);
#if !PROD_BUILD
module_registrar.setup_reflection(store);
#endif
}
- (Maps to #3 above)
/***** .h *****/
class DataRegistrar {
virtual void register_data_types(lib::store& store) const = 0;
virtual void setup_reflection(lib::store& store) const = 0;
friend void register_data(const DataRegistrar& module_registrar, lib::store& store);
};
void register_data(const DataRegistrar& module_registrar, lib::store& store) {
module_registrar.register_data_types(store);
#if !PROD_BUILD
module_registrar.setup_reflection(store);
#endif
}
[1] "modules" as in areas/directories/domains of the app, not c 20 modules
CodePudding user response:
Let's start from #3:
friend
breaks encapsulation and that's why Scott Meyers doesn't suggest it. I also don't suggest it unless you really need it. Eg: CRTP's private contstruct friend trick to prevent typo.
I would suggest #1 since all the works are done by the class extending the inerface DataRegistrar
. Also you don't want others to fiddle with op_1()
and op_2()
.
If using free function, it requires the instance having op_1()
and op_2()
to be exposed and increases the chance to be misused.