I have a code base where some common functions need to be called in order before and after some unique function
Eg:
common1();
common2();
unique(); // note: unique returns void but can have any number of arguments
common3();
common4();
A problem is, anytime a new unique is made, or anytime more commonX functions are added then every place in the code where this pattern is required has to be made to match.
One solution is to use a macro
#define DO_UNIQUE_CORRECTLY(unique_code_block) \
common1(); \
common2(); \
unique_code_block \
common3(); \
commont(); \
...
void someFunc1(arg1) {
DO_UNIQUE_CORRECTLY({
unique1(arg1);
});
}
void someFunc2(arg1, arg2) {
DO_UNIQUE_CORRECTLY({
unique2(arg1, arg2);
});
}
That works. The question is: Is there a non-Macro C++ way to do this with ZERO overhead AND discourage mistakes AND and not be overly verbose to use.
Note: if it's not obvious, the "not overly verbose" requirement means the functions I'm calling might have lots of parameters with very large types. Having to copy and paste the list of types = "overly verbose"
One solution I know of is something like this
class Helper {
Helper() {
common1();
common2();
}
~Helper() {
common3();
common4();
}
}
void someFunc1(arg1) {
Helper helper;
unique1(arg1);
}
void someFunc2(arg1, arg2) {
Helper helper;
unique2(arg1, arg2);
}
The problem with this solution IMO is it's seems easy to insert stuff unaware that you're doing it wrong
void someFunc2(arg1, arg2) {
Helper helper;
doExtra1(); // bad
unique2(arg1, arg2);
doExtra2(); // bad
}
Like imagine that common2 is perfRecordStartTime and common3 is perfRecordEndTime. In that case you don't want anything extra before/after unique2.
You could argue the same issue with the macro
void someFunc2(arg1, arg2) {
DO_UNIQUE_CORRECTLY({
doExtra1(); // bad
unique2(arg1, arg2);
doExtra2(); // bad
});
});
But rename DO_UNIQUE_CORRECTLY to say TIME_FUNCTION and Helper to TimingHelper
void someFunc2(arg1, arg2) {
TIME_FUNCTION({
doExtra1(); // clearly bad
unique2(arg1, arg2);
doExtra2(); // clearly bad
});
});
vs
void someFunc2(arg1, arg2) {
TimingHelper helper;
doExtra1(); // bad?
unique2(arg1, arg2);
doExtra2(); // bad?
}
TimingHelper so doing magic stuff here. Maybe it's just an opinion but this helper pattern seems more error prone for this case than the macro pattern.
Is it also some possibly super tiny but non-zero cost calling into the Helper constructor/destructor vs the inline code of the macro?
Another is to use lambdas
template<typename Func>
void DoUniqueCorrectly(Func fn) {
common1();
common2();
fn();
common3();
common4();
}
void someFunc1(arg1) {
DoUniqueCorrectly([&]() {
unique1(arg1);
});
}
void someFunc2(arg1, arg2) {
DoUniqueCorrectly([&]() {
unique2(arg1, arg2);
});
}
The problem with this one is as far as I understand, lambdas are really building an object/tuple to hold on to references to the closed over arguments. To put another way it's almost like this
void someFunc2(type1 arg1, type2 arg2)
std::tuple<const type1&, const type2&> c = {arg1, arg2};
DoUniqueCorrectly([&]() {
unique2(std::get<0>(c), std::get<1>(c));
});
}
So there is overhead of this tuple being initialized. Will that all be optimized out so the code will have the same overhead as the macro?
Let me add one more wrinkle. uniqueX can be a more complex function call. For example
void SomeClass::someFunc2(arg1, arg2) {
DO_UNIQUE_CORRECTLY({
getContext()->getThing()->unique2(arg1, arg2);
});
}
Is there another solution?
PS: I get that if any overhead exists it's small. I still want to know if there is a zero overhead non-macro C++ way of doing this.