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.
Question
How to document it elegantly? (cute && concise && no additional CPU cost)
Example
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();
}
My poor solutions
1. Comment / doc
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.
2. Rename the "func()"
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)
3. Virtual class with "func()=0"
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.
4. Static function
(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.
From the user side, I would use crtp to create a callback interface, and force Users to use it. For example:
template <typename T>
struct ICallbacks
{
void foo()
{
static_cast<T*>(this)->foo();
}
};
Users should inherit from this interface and implement foo()
callback
struct User : public ICallbacks<User>
{
void foo() {std::cout << "User call back" << std::endl;}
};
The nice thing about it is that if Library
is using ICallback
interface and User
forget to implement foo()
you will get a nice compiler error message.
Note that there is no virtual function, so no performance penalty here.
From the library side, I would only call those callbacks via its interfaces (in this case ICallback
). Following OP in using pointers, I would do something like this:
template <typename T>
struct Library
{
ICallbacks<T> *node = 0;
void utility()
{
assert(node != nullptr);
node->foo();
}
};
Note that things get auto documented in this way. It is very explicit that you are using a callback interface, and node
is the object who has those functions.
Bellow a complete working example:
#include <iostream>
#include <cassert>
template <typename T>
struct ICallbacks
{
void foo()
{
static_cast<T*>(this)->foo();
}
};
struct User : public ICallbacks<User>
{
void foo() {std::cout << "User call back" << std::endl;}
};
template <typename T>
struct Library
{
ICallbacks<T> *node = 0;
void utility()
{
assert(node != nullptr);
node->foo();
}
};
int main()
{
User user;
Library<User> l;
l.node = &user;
l.utility();
}
Test.h
#ifndef TEST_H
#define TEST_H
// User Class Prototype Declarations
class User;
// Templated Wrapper Class To Contain Callback Functions
// User Will Inherit From This Using Their Own Class As This
// Class's Template Parameter
template <class T>
class Wrapper {
public:
// Function Template For Callback Methods.
template<class U>
auto Callback(...) {};
};
// Templated Library Class Defaulted To User With The Utility Function
// That Provides The Invoking Of The Call Back Method
template<class T = User>
class Library {
public:
T* node = nullptr;
void utility() {
T::Callback(node);
}
};
// User Class Inherited From Wrapper Class Using Itself As Wrapper's Template Parameter.
// Call Back Method In User Is A Static Method And Takes A class Wrapper* Declaration As
// Its Parameter
class User : public Wrapper<User> {
public:
static void Callback( class Wrapper* ) { std::cout << "Callback was called.\n"; }
};
#endif // TEST_H
main.cpp
#include "Test.h"
int main() {
Library<User> l;
l.utility();
return 0;
}
Output
Callback was called.
I was able to compile, build and run this without error in VS2017 CE on Windows 7 - 64bit Intel Core 2 Quad Extreme.
Any Thoughts?
I would recommend to name the wrapper class appropriately, then for each specific call back function that has a unique purpose name them accordingly within the wrapper class.
Edit
After playing around with this "template magic" well there is no such thing...
I had commented out the function template in the Wrapper
class and found that it is not needed. Then I commented out the class Wrapper*
that is the argument list for the Callback()
in User
. This gave me a compiler error that stated that User::Callback()
does not take 0
arguments. So I looked back at Wrapper
since User
inherits from it. Well at this point Wrapper
is an empty class template.
This lead me to look at Library
. Library has a pointer to User
as a public member and a utility()
function that invokes User's
static Callback
method. It is here that the invoking method is taking a pointer to a User
object as its parameter. So it lead me to try this:
class User; // Prototype
class A{}; // Empty Class
template<class T = User>
class Library {
public:
T* node = nullptr;
void utility() {
T::Callback(node);
}
};
class User : public A {
public:
static void Callback( A* ) { std::cout << "Callback was called.\n"; }
};
And this compiles and builds correctly as the simplified version. However; when I thought about it; the template version is better because it is deduced at compile time and not run time. So when we go back to using templates javaLover had asked me what class Wrapper*
means or is within the argument list for the Callback
method within the User
class.
I'll try to explain this as clearly as I can but first the wrapper Class is just an empty template shell that User
will inherit from and it does nothing but act as a base class and it now looks like this:
template<class T>
class Wrapper { // Could Be Changed To A More Suitable Name Such As Shell or BaseShell
};
When we look at the User
class:
class User : public Wrapper<User> {
public:
static void Callback( class Wrapper* ) { // print statement }
};
We see that User
is a non-template class that inherits from a template class but uses itself as the template's argument. It contains a public static method
and this method doesn't return any thing but it does take a single parameter; this is also evident in the Library
class that has its template parameter as a User
class. When the Library's
utility()
method invokes User's
Callback()
method the parameter that the Library is expecting is a pointer to a User
object. So when we go back to the User
class instead of declaring it as a User*
pointer directly in its declaration I'm using the empty class template that it inherits from. However if you try to do this:
class User : public Wrapper<User> {
public:
static void Callback( Wrapper* ) { // print statement }
};
You should get a message that Wrapper*
is missing it's argument list. We could just do Wrapper<User>*
here but that is redundant since we already see that User is inheriting from Wrapper that takes itself. So we can fix this and make it cleaner just by prefixing the Wrapper*
with the class
keyword since it is a class template. Hence the template magic... well there is no magic here... just compiler intrinsic and optimizations.
While I know that I don't answer your specific question (how to document the not-to-be-deleted function) I would solve your problem (keeping the seemingly unused callback function in the code base) by instantiating Library<User>
and calling the utility()
function in a unit test (or maybe it should rather be called an API test...). This solution would probably scale to your real world example too, as long as you don't have to check each possible combination of library classes and callback functions.
If you are lucky enough to work in an organization where successful unit tests and code review are required before changes go into the code base this would require a change to the unit tests before anyone could remove the User::func()
function and such a change would probably catch the attention of a reviewer.
Then again, you know your environment and I don't, and I'm aware that this solution doesn't fit all situations.
Here is a solution using a Traits class:
// Library.h:
template<class T> struct LibraryTraits; // must be implemented for every User-class
template<class T> class Library {
public:
T* node=nullptr;
void utility() {
LibraryTraits<T>::func(node);
}
};
// User.h:
class User { };
// must only be implemented if User is to be used by Library (and can be implemented somewhere else)
template<> struct LibraryTraits<User> {
static void func(User* node) { std::cout << "LibraryTraits<User>::func(" << node << ")\n"; }
};
// main.cpp:
int main() {
Library<User> li; li.utility();
}
Advantages:
- It is obvious by the naming that
LibraryTraits<User>
is only required for interfacingUser
byLibrary
(and can be removed, once eitherLibrary
orUser
gets removed. LibraryTraits
can be specialized independent ofLibrary
andUser
Disadvantages:
- No easy access to private members of
User
(makingLibraryTraits
a friend ofUser
would remove the independence). - If the same
func
is needed for differentLibrary
classes multipleTrait
classes need to be implemented (could be solved by default implementations inheriting from otherTrait
classes).
This heavily reminds an old good Policy-Based Design, except in your case you do not inherit the Library
class from the User
class.
Good names are the best friends of any API. Combine this and the well-known patter of Policy-Based Design (well-known is very important because the class names with the word Policy
in it will immediately ring the bell in many readers of the code) and, I assume, you get a well self-documenting code.
Inheritance won't give you any performance overhead, but will give you an ability to have the
Callback
as a protected method, that will give some hint that it is supposed to be inherited and be used somewhere.Have clearly standing-out and consistent naming among multiple
User
-like classes (e.g.SomePolicyOfSomething
in the manner of aforementioned Policy-Based Design), as well as, the template arguments for theLibrary
(e.gSomePolicy
, or I would call itTSomePolicy
).Having
using
declaration of theCallback
in theLibrary
class might give much clearer and earlier errors (e.g. from IDE, or modern clang, visial studio syntax parsers for IDE).
Another arguable option might be a static_assert
if you have C++>=11. But in this case it must be used in every User
-like class ((.
Not a direct answer to your question on how to document it, but something to consider:
If your Library template requires an implementation of someFunction()
for each class to be used in it, i'd recommend adding it as a template argument.
#include <functional>
template<class Type, std::function<void(Type*)> callback>
class Library {
// Some Stuff...
Type* node = nullptr;
public:
void utility() {
callback(this->node);
}
};
Might make it even more explicit, so that other devs know it's needed.
abstract class is the best way to enforce the function not to be deleted. So i recommend implementing the base class with pure virtual function, so that derived has to define the function. OR second solution would be to have function pointers so that performance will be saved by avoiding extra overhead of V-table creation and calling.
If it is not obvious that func()
is needed in User
, then I'd argue you're violating the single responsibility principle. Instead create an adapter class of which User
as a member.
class UserCallback {
public:
void func();
private:
User m_user;
}
That way the existance of UserCallback
documents that func()
is an external call back, and separates out Library
's need of a callback from the actual responsibilities of User
.
来源:https://stackoverflow.com/questions/43445184/how-to-self-document-a-callback-function-that-is-called-by-template-library-clas