How to self-document a callback function that is called by template library class?

本小妞迷上赌 提交于 2019-12-03 09:55:16

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 interfacing User by Library (and can be removed, once either Library or User gets removed.
  • LibraryTraits can be specialized independent of Library and User

Disadvantages:

  • No easy access to private members of User (making LibraryTraits a friend of User would remove the independence).
  • If the same func is needed for different Library classes multiple Trait classes need to be implemented (could be solved by default implementations inheriting from other Trait 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 the Library (e.g SomePolicy, or I would call it TSomePolicy).

  • Having using declaration of the Callback in the Library 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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!