I have a function User::func()
(callback) that would be called by a template class (Library<T>
).
In the first iteration of development, everyone know that func()
serves only for that single purpose.
A few months later, most members forget what func()
is for.
After some heavy refactoring, the func()
is sometimes deleted by some coders.
At first, I didn't think this is a problem at all.
However, after I re-encountered this pattern several times, I think I need some counter-measure.
How to document it elegantly? (cute && concise && no additional CPU cost)
Here is a simplified code:-
(The real world problem is scattering around 10+ library-files & 20+ user files & 40+ functions.)
Library.h
template<class T> class Library{
public: T* node=nullptr;
public: void utility(){
node->func(); //#1
}
};
User.h
class User{
public: void func(){/** some code*/} //#1
//... a lot of other functions ...
// some of them are also callback of other libraries
};
main.cpp
int main(){
Library<User> li; .... ; li.utility();
}
As the first workaround, I tend to add a comment like this:-
class User{
/** This function is for "Library" callback */
public: void func(){/** some code*/}
};
But it gets dirty pretty fast - I have to add it to every "func" in every class.
In real case, I tend to prefix function name like this:-
class User{
public: void LIBRARY_func(){/** some code*/}
};
It is very noticeable, but the function name is now very longer.
(especially when Library
-class has longer class name)
I am considering to create an abstract class as interface for the callback.
class LibraryCallback{
public: virtual void func()=0;
};
class User : public LibraryCallback{
public: virtual void func(){/** some code*/}
};
It provides feeling that func()
is for something-quite-external. :)
However, I have to sacrifice virtual-calling cost (v-table).
In performance-critical cases, I can't afford it.
(idea from Daniel Jour in comment, thank!)
Almost 1 month later, here is how I use :-
Library.h
template<class T> class Library{
public: T* node=nullptr;
public: void utility(){
T::func(node); //#1
}
};
User.h
class User{
public: static void func(Callback*){/** some code*/}
};
main.cpp
int main(){
Library<User> li;
}
It is probably cleaner, but still lack self-document.
func
is not a feature of User
. It is a feature of the User
-Library<T>
coupling.
Placing it in User
if it doesn't have clear semantics outside of Library<T>
use is a bad idea. If it does have clear semantics, it should say what it does, and deleting it should be an obviously bad idea.
Placing it in Library<T>
cannot work, because its behavior is a function of the T
in Library<T>
.
The answer is to place it in neither spot.
template<class T> struct tag_t{ using type=T; constexpr tag_t(){} };
template<class T> constexpr tag_t<T> tag{};
Now in Library.h
:
struct ForLibrary;
template<class T> class Library{
public: T* node=nullptr;
public: void utility(){
func( tag<ForLibrary>, node ); // #1
}
};
in User.h
:
struct ForLibrary;
class User{
/** This function is for "Library" callback */
public:
friend void func( tag_t<ForLibrary>, User* self ) {
// code
}
};
or just put this into the same namespace as User
, or the same namespace as ForLibrary
:
friend func( tag_t<ForLibrary>, User* self );
Before deleting func
, you'll track down ForLibrary
.
It is no longer part of the "public interface" of User
, so doesn't clutter it up. It is either a friend (a helper), or a free function in the same namespace of either User
or Library
.
You can implement it where you need a Library<User>
instead of in User.h
or Library.h
, especially if it just uses public interfaces of User
.
The techniques used here are "tag dispatching", "argument dependent lookup", "friend functions" and preferring free functions over methods.