如何自我记录模板库class调用的回调函数?
How to self-document a callback function that is called by template library class?
我有一个函数 User::func()
(回调),模板 class (Library<T>
) 会调用它。
在开发的第一次迭代中,每个人都知道 func()
仅用于该单一目的。
几个月后,大多数成员忘记了 func()
的用途。
经过一些重大重构后,func()
有时会被某些编码人员删除。
起初,我根本不认为这是个问题。
但是,在我多次遇到这种模式之后,我觉得我需要一些对策。
问题
如何优雅地记录下来? (可爱&&简洁&&无额外CPU成本)
例子
这是一个简化的代码:-
(现实世界的问题是分散在 10 多个库文件和 20 多个用户文件和 40 多个函数周围。)
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();
}
我糟糕的解决方案
1。评论/文档
作为第一个解决方法,我倾向于添加这样的评论:-
class User{
/** This function is for "Library" callback */
public: void func(){/** some code*/}
};
但它变脏的速度非常快 - 我必须将它添加到每个 class 中的每个 "func"。
2。重命名 "func()"
在实际情况下,我倾向于像这样为函数名添加前缀:-
class User{
public: void LIBRARY_func(){/** some code*/}
};
很明显,但是函数名现在很长。
(特别是当 Library
-class 有更长的 class 名称时)
3。虚拟 class 与 "func()=0"
我正在考虑创建一个抽象 class 作为回调的接口。
class LibraryCallback{
public: virtual void func()=0;
};
class User : public LibraryCallback{
public: virtual void func(){/** some code*/}
};
给人的感觉是func()
是为了一些非常外在的东西。 :)
但是,我必须牺牲虚拟呼叫成本 (v-table).
在性能关键的情况下,我买不起。
4。静态函数
(Daniel Jour 的想法在评论中,谢谢!)
差不多 1 个月后,我是这样使用的:-
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;
}
它可能更干净,但仍然缺少自文档。
摘要class是最好的强制功能不被删除的方法。所以我建议用纯虚函数实现基class,这样派生就必须定义函数。
或者第二种解决方案是使用函数指针,这样可以通过避免 V-table 创建和调用的额外开销来节省性能。
func
不是 User
的特征。这是 User
-Library<T>
耦合的一个特征。
如果它在 Library<T>
使用之外没有明确的语义,则将它放在 User
中是一个坏主意。如果它确实有明确的语义,它应该说明它的作用,删除它应该是一个明显的坏主意。
将它放在 Library<T>
中是行不通的,因为它的行为是 Library<T>
中 T
的函数。
答案是两个地方都不放。
template<class T> struct tag_t{ using type=T; constexpr tag_t(){} };
template<class T> constexpr tag_t<T> tag{};
现在 Library.h
:
struct ForLibrary;
template<class T> class Library{
public: T* node=nullptr;
public: void utility(){
func( tag<ForLibrary>, node ); // #1
}
};
在User.h
中:
struct ForLibrary;
class User{
/** This function is for "Library" callback */
public:
friend void func( tag_t<ForLibrary>, User* self ) {
// code
}
};
或者只是将其放入与 User
、 或 相同的名称空间中 ForLibrary
:
friend func( tag_t<ForLibrary>, User* self );
在删除 func
之前,您将找到 ForLibrary
。
它不再是 User
的 "public interface" 的一部分,所以不要弄乱它。它可以是朋友(帮助者),也可以是 User
或 Library
.
相同命名空间中的自由函数
您可以在需要 Library<User>
的地方实现它,而不是在 User.h
或 Library.h
中实现它,特别是如果它只使用 User
的 public 接口.
这里使用的技术是 "tag dispatching"、"argument dependent lookup"、"friend functions" 并且更喜欢自由函数而不是方法。
从用户端来说,我会用crtp创建一个回调接口,强制用户使用它。例如:
template <typename T>
struct ICallbacks
{
void foo()
{
static_cast<T*>(this)->foo();
}
};
用户应继承此接口并实现foo()
回调
struct User : public ICallbacks<User>
{
void foo() {std::cout << "User call back" << std::endl;}
};
它的好处是,如果 Library
使用 ICallback
接口并且 User
忘记实现 foo()
,您将得到一个很好的编译器错误消息。
注意没有虚函数,所以这里没有性能损失。
在库方面,我只会通过其接口调用那些回调(在本例中为 ICallback
)。按照 OP 使用指针,我会做这样的事情:
template <typename T>
struct Library
{
ICallbacks<T> *node = 0;
void utility()
{
assert(node != nullptr);
node->foo();
}
};
请注意,事情会以这种方式自动记录下来。很明显,你正在使用回调接口,node
是具有这些功能的对象。
下面是一个完整的工作示例:
#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;
}
输出
Callback was called.
我能够在 Windows 7 - 64 位 Intel Core 2 Quad Extreme 上的 VS2017 CE 中编译、构建和 运行 这没有错误。
Any Thoughts?
我建议适当地命名包装器 class,然后对于每个具有独特用途的特定回调函数,在包装器中相应地命名它们 class.
编辑
玩过这个“模板魔术”之后,就没有这样的东西了......
Wrapper
class中的函数模板我已经注释掉了,发现不需要了。然后我注释掉 class Wrapper*
,它是 User
中 Callback()
的参数列表。这给了我一个编译器错误,指出 User::Callback()
不接受 0
参数。所以我回头看了看 Wrapper
因为 User
继承自它。那么此时 Wrapper
是一个空的 class 模板。
这让我看 Library
。库有一个指向 User
作为 public 成员的指针和一个调用 User's
static Callback
方法的 utility()
函数。正是在这里,调用方法将指向 User
对象的指针作为其参数。所以它引导我尝试这个:
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"; }
};
并且这可以作为简化版本正确编译和构建。然而;当我想到它时;模板版本更好,因为它是在编译时推导出来的,而不是 运行 时间。因此,当我们回到使用模板时,javaLover 问我 class Wrapper*
是什么意思,或者在 User
class.[=52= 中的 Callback
方法的参数列表中]
我会尽可能清楚地解释这一点,但首先包装器 Class 只是一个空模板 shell,User
将继承它,它除了作为基础 class 现在看起来像这样:
template<class T>
class Wrapper { // Could Be Changed To A More Suitable Name Such As Shell or BaseShell
};
当我们查看 User
class:
class User : public Wrapper<User> {
public:
static void Callback( class Wrapper* ) { // print statement }
};
我们看到 User
是一个非模板 class,它继承自模板 class 但将其自身用作模板的参数。它包含一个 public 静态方法
这个方法没有 return 任何东西,但它确实需要一个参数;这在 Library
class 中也很明显,它的模板参数是 User
class。当 Library's
utility()
方法调用 User's
Callback()
方法时,库期望的参数是指向 User
对象的指针。因此,当我们回到 User
class 而不是直接在其声明中将其声明为 User*
指针时,我使用的是它继承自的空 class 模板。但是,如果您尝试这样做:
class User : public Wrapper<User> {
public:
static void Callback( Wrapper* ) { // print statement }
};
您应该收到一条消息,指出 Wrapper*
缺少它的参数列表。我们可以在这里做 Wrapper<User>*
但这是多余的,因为我们已经看到 User 是从 Wrapper 继承的。所以我们可以通过在 Wrapper*
前面加上 class
关键字来解决这个问题并使其更清晰,因为它是一个 class 模板。因此,模板魔法...好吧,这里没有魔法...只有编译器内在和优化。
虽然我知道我不会回答您的具体问题(如何记录不可删除的函数),但我会通过实例化来解决您的问题(在代码库中保留看似未使用的回调函数) Library<User>
并在单元测试中调用 utility()
函数(或者它应该被称为 API 测试...)。只要您不必检查库 类 和回调函数的每个可能组合,此解决方案也可能会扩展到您的真实示例。
如果您有幸在一个组织中工作,在该组织中,在更改进入代码库之前需要成功的单元测试和代码审查,这将需要更改单元测试,然后任何人都可以删除 User::func()
功能,这样的更改可能会引起审阅者的注意。
话又说回来,你知道你的环境而我不知道,而且我知道这个解决方案并不适合所有情况。
不是对您关于如何记录它的问题的直接回答,而是需要考虑的事项:
如果您的库模板需要为其中使用的每个 class 实现 someFunction()
,我建议将其添加为模板参数。
#include <functional>
template<class Type, std::function<void(Type*)> callback>
class Library {
// Some Stuff...
Type* node = nullptr;
public:
void utility() {
callback(this->node);
}
};
可能会使其更加明确,以便其他开发人员知道它是必需的。
这是一个使用 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();
}
优势:
- 从命名中可以明显看出,
LibraryTraits<User>
仅在通过 Library
连接 User
时需要(并且可以删除,一旦 Library
或 User
被删除。
LibraryTraits
可以独立于 Library
和 User
缺点:
- 无法轻松访问
User
的私人成员(让 LibraryTraits
成为 User
的朋友会消除独立性)。
- 如果不同的
Library
需要相同的func
class需要实现多个Trait
classes(可以默认解决从其他 Trait
classes). 继承的实现
这让我想起了一个古老的好东西 Policy-Based Design,除了在你的情况下你没有从 User
class 继承 Library
class。
好名字是任何 API 最好的朋友。结合这个和众所周知的基于策略设计的模式(知名度非常重要,因为 class 名称中带有单词 Policy
会立即在代码的许多读者中敲响警钟)而且,我假设,你会得到一个很好的自我记录代码。
继承不会给您带来任何性能开销,但会让您能够将 Callback
作为受保护的方法,这会给出一些提示,表明它应该是继承并在某处使用。
在多个 User
-like classes 中具有明显突出和一致的命名(例如 SomePolicyOfSomething
以上述基于策略的设计方式) ,以及 Library
的模板参数(例如 SomePolicy
,或者我称之为 TSomePolicy
)。
using
在 Library
class 中声明 Callback
可能会给出更清晰和更早的错误(例如来自 IDE,或 IDE).
的现代 clang、visial studio 语法分析器
如果您的 C++>=11,另一个有争议的选项可能是 static_assert
。但在这种情况下,它必须用于每个 User
-like class ((.
如果 User
中需要 func()
并不明显,那么我认为您违反了 single responsibility principle. Instead create an adapter class 其中 [=12] =] 作为会员。
class UserCallback {
public:
void func();
private:
User m_user;
}
这样 UserCallback
的存在证明 func()
是一个外部回调,并将 Library
对回调的需求与 [=12] 的实际职责分开=].
我有一个函数 User::func()
(回调),模板 class (Library<T>
) 会调用它。
在开发的第一次迭代中,每个人都知道 func()
仅用于该单一目的。
几个月后,大多数成员忘记了 func()
的用途。
经过一些重大重构后,func()
有时会被某些编码人员删除。
起初,我根本不认为这是个问题。
但是,在我多次遇到这种模式之后,我觉得我需要一些对策。
问题
如何优雅地记录下来? (可爱&&简洁&&无额外CPU成本)
例子
这是一个简化的代码:-
(现实世界的问题是分散在 10 多个库文件和 20 多个用户文件和 40 多个函数周围。)
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();
}
我糟糕的解决方案
1。评论/文档
作为第一个解决方法,我倾向于添加这样的评论:-
class User{
/** This function is for "Library" callback */
public: void func(){/** some code*/}
};
但它变脏的速度非常快 - 我必须将它添加到每个 class 中的每个 "func"。
2。重命名 "func()"
在实际情况下,我倾向于像这样为函数名添加前缀:-
class User{
public: void LIBRARY_func(){/** some code*/}
};
很明显,但是函数名现在很长。
(特别是当 Library
-class 有更长的 class 名称时)
3。虚拟 class 与 "func()=0"
我正在考虑创建一个抽象 class 作为回调的接口。
class LibraryCallback{
public: virtual void func()=0;
};
class User : public LibraryCallback{
public: virtual void func(){/** some code*/}
};
给人的感觉是func()
是为了一些非常外在的东西。 :)
但是,我必须牺牲虚拟呼叫成本 (v-table).
在性能关键的情况下,我买不起。
4。静态函数
(Daniel Jour 的想法在评论中,谢谢!)
差不多 1 个月后,我是这样使用的:-
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;
}
它可能更干净,但仍然缺少自文档。
摘要class是最好的强制功能不被删除的方法。所以我建议用纯虚函数实现基class,这样派生就必须定义函数。 或者第二种解决方案是使用函数指针,这样可以通过避免 V-table 创建和调用的额外开销来节省性能。
func
不是 User
的特征。这是 User
-Library<T>
耦合的一个特征。
如果它在 Library<T>
使用之外没有明确的语义,则将它放在 User
中是一个坏主意。如果它确实有明确的语义,它应该说明它的作用,删除它应该是一个明显的坏主意。
将它放在 Library<T>
中是行不通的,因为它的行为是 Library<T>
中 T
的函数。
答案是两个地方都不放。
template<class T> struct tag_t{ using type=T; constexpr tag_t(){} };
template<class T> constexpr tag_t<T> tag{};
现在 Library.h
:
struct ForLibrary;
template<class T> class Library{
public: T* node=nullptr;
public: void utility(){
func( tag<ForLibrary>, node ); // #1
}
};
在User.h
中:
struct ForLibrary;
class User{
/** This function is for "Library" callback */
public:
friend void func( tag_t<ForLibrary>, User* self ) {
// code
}
};
或者只是将其放入与 User
、 或 相同的名称空间中 ForLibrary
:
friend func( tag_t<ForLibrary>, User* self );
在删除 func
之前,您将找到 ForLibrary
。
它不再是 User
的 "public interface" 的一部分,所以不要弄乱它。它可以是朋友(帮助者),也可以是 User
或 Library
.
您可以在需要 Library<User>
的地方实现它,而不是在 User.h
或 Library.h
中实现它,特别是如果它只使用 User
的 public 接口.
这里使用的技术是 "tag dispatching"、"argument dependent lookup"、"friend functions" 并且更喜欢自由函数而不是方法。
从用户端来说,我会用crtp创建一个回调接口,强制用户使用它。例如:
template <typename T>
struct ICallbacks
{
void foo()
{
static_cast<T*>(this)->foo();
}
};
用户应继承此接口并实现foo()
回调
struct User : public ICallbacks<User>
{
void foo() {std::cout << "User call back" << std::endl;}
};
它的好处是,如果 Library
使用 ICallback
接口并且 User
忘记实现 foo()
,您将得到一个很好的编译器错误消息。
注意没有虚函数,所以这里没有性能损失。
在库方面,我只会通过其接口调用那些回调(在本例中为 ICallback
)。按照 OP 使用指针,我会做这样的事情:
template <typename T>
struct Library
{
ICallbacks<T> *node = 0;
void utility()
{
assert(node != nullptr);
node->foo();
}
};
请注意,事情会以这种方式自动记录下来。很明显,你正在使用回调接口,node
是具有这些功能的对象。
下面是一个完整的工作示例:
#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;
}
输出
Callback was called.
我能够在 Windows 7 - 64 位 Intel Core 2 Quad Extreme 上的 VS2017 CE 中编译、构建和 运行 这没有错误。
Any Thoughts?
我建议适当地命名包装器 class,然后对于每个具有独特用途的特定回调函数,在包装器中相应地命名它们 class.
编辑
玩过这个“模板魔术”之后,就没有这样的东西了......
Wrapper
class中的函数模板我已经注释掉了,发现不需要了。然后我注释掉 class Wrapper*
,它是 User
中 Callback()
的参数列表。这给了我一个编译器错误,指出 User::Callback()
不接受 0
参数。所以我回头看了看 Wrapper
因为 User
继承自它。那么此时 Wrapper
是一个空的 class 模板。
这让我看 Library
。库有一个指向 User
作为 public 成员的指针和一个调用 User's
static Callback
方法的 utility()
函数。正是在这里,调用方法将指向 User
对象的指针作为其参数。所以它引导我尝试这个:
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"; }
};
并且这可以作为简化版本正确编译和构建。然而;当我想到它时;模板版本更好,因为它是在编译时推导出来的,而不是 运行 时间。因此,当我们回到使用模板时,javaLover 问我 class Wrapper*
是什么意思,或者在 User
class.[=52= 中的 Callback
方法的参数列表中]
我会尽可能清楚地解释这一点,但首先包装器 Class 只是一个空模板 shell,User
将继承它,它除了作为基础 class 现在看起来像这样:
template<class T>
class Wrapper { // Could Be Changed To A More Suitable Name Such As Shell or BaseShell
};
当我们查看 User
class:
class User : public Wrapper<User> {
public:
static void Callback( class Wrapper* ) { // print statement }
};
我们看到 User
是一个非模板 class,它继承自模板 class 但将其自身用作模板的参数。它包含一个 public 静态方法
这个方法没有 return 任何东西,但它确实需要一个参数;这在 Library
class 中也很明显,它的模板参数是 User
class。当 Library's
utility()
方法调用 User's
Callback()
方法时,库期望的参数是指向 User
对象的指针。因此,当我们回到 User
class 而不是直接在其声明中将其声明为 User*
指针时,我使用的是它继承自的空 class 模板。但是,如果您尝试这样做:
class User : public Wrapper<User> {
public:
static void Callback( Wrapper* ) { // print statement }
};
您应该收到一条消息,指出 Wrapper*
缺少它的参数列表。我们可以在这里做 Wrapper<User>*
但这是多余的,因为我们已经看到 User 是从 Wrapper 继承的。所以我们可以通过在 Wrapper*
前面加上 class
关键字来解决这个问题并使其更清晰,因为它是一个 class 模板。因此,模板魔法...好吧,这里没有魔法...只有编译器内在和优化。
虽然我知道我不会回答您的具体问题(如何记录不可删除的函数),但我会通过实例化来解决您的问题(在代码库中保留看似未使用的回调函数) Library<User>
并在单元测试中调用 utility()
函数(或者它应该被称为 API 测试...)。只要您不必检查库 类 和回调函数的每个可能组合,此解决方案也可能会扩展到您的真实示例。
如果您有幸在一个组织中工作,在该组织中,在更改进入代码库之前需要成功的单元测试和代码审查,这将需要更改单元测试,然后任何人都可以删除 User::func()
功能,这样的更改可能会引起审阅者的注意。
话又说回来,你知道你的环境而我不知道,而且我知道这个解决方案并不适合所有情况。
不是对您关于如何记录它的问题的直接回答,而是需要考虑的事项:
如果您的库模板需要为其中使用的每个 class 实现 someFunction()
,我建议将其添加为模板参数。
#include <functional>
template<class Type, std::function<void(Type*)> callback>
class Library {
// Some Stuff...
Type* node = nullptr;
public:
void utility() {
callback(this->node);
}
};
可能会使其更加明确,以便其他开发人员知道它是必需的。
这是一个使用 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();
}
优势:
- 从命名中可以明显看出,
LibraryTraits<User>
仅在通过Library
连接User
时需要(并且可以删除,一旦Library
或User
被删除。 LibraryTraits
可以独立于Library
和User
缺点:
- 无法轻松访问
User
的私人成员(让LibraryTraits
成为User
的朋友会消除独立性)。 - 如果不同的
Library
需要相同的func
class需要实现多个Trait
classes(可以默认解决从其他Trait
classes). 继承的实现
这让我想起了一个古老的好东西 Policy-Based Design,除了在你的情况下你没有从 User
class 继承 Library
class。
好名字是任何 API 最好的朋友。结合这个和众所周知的基于策略设计的模式(知名度非常重要,因为 class 名称中带有单词 Policy
会立即在代码的许多读者中敲响警钟)而且,我假设,你会得到一个很好的自我记录代码。
继承不会给您带来任何性能开销,但会让您能够将
Callback
作为受保护的方法,这会给出一些提示,表明它应该是继承并在某处使用。在多个
User
-like classes 中具有明显突出和一致的命名(例如SomePolicyOfSomething
以上述基于策略的设计方式) ,以及Library
的模板参数(例如SomePolicy
,或者我称之为TSomePolicy
)。using
在Library
class 中声明Callback
可能会给出更清晰和更早的错误(例如来自 IDE,或 IDE). 的现代 clang、visial studio 语法分析器
如果您的 C++>=11,另一个有争议的选项可能是 static_assert
。但在这种情况下,它必须用于每个 User
-like class ((.
如果 User
中需要 func()
并不明显,那么我认为您违反了 single responsibility principle. Instead create an adapter class 其中 [=12] =] 作为会员。
class UserCallback {
public:
void func();
private:
User m_user;
}
这样 UserCallback
的存在证明 func()
是一个外部回调,并将 Library
对回调的需求与 [=12] 的实际职责分开=].