1. 从Command模式到泛化仿函数的设计演进
在面向对象设计中,Command模式是一种行为设计模式,它将请求封装为独立的对象,使你可以参数化客户端对象与不同的请求、队列或日志请求,并支持可撤销的操作。这种模式的核心价值在于解耦请求的发起者(Invoker)和请求的执行者(Receiver)。
1.1 Command模式的核心结构
典型的Command模式包含以下几个关键组件:
- Command接口:声明执行操作的接口
- ConcreteCommand:将接收者对象绑定到一组动作上,实现执行方法
- Invoker:要求命令执行请求
- Receiver:知道如何执行与请求相关的操作
cpp复制class Command {
public:
virtual ~Command() {}
virtual void Execute() = 0;
};
class Receiver {
public:
void Action() { /* 具体操作实现 */ }
};
class ConcreteCommand : public Command {
public:
ConcreteCommand(Receiver* receiver) : receiver_(receiver) {}
void Execute() override { receiver_->Action(); }
private:
Receiver* receiver_;
};
class Invoker {
public:
void SetCommand(Command* command) { command_ = command; }
void ExecuteCommand() { command_->Execute(); }
private:
Command* command_;
};
1.2 转发式命令与主动式命令的区别
在实践应用中,Command模式通常会演化为两种具体实现形式:
-
转发式命令(Forwarding Command):
- 仅将操作委托给Receiver对象
- 不包含额外业务逻辑
- 可高度泛化和复用
-
主动式命令(Active Command):
- 包含复杂的业务逻辑
- 可能调用多个Receiver的方法
- 通常与特定应用场景紧密耦合
提示:转发式命令因其简单性和可复用性,非常适合通过泛型编程技术进行抽象和泛化,这正是泛化仿函数的设计出发点。
2. 泛化仿函数的设计原理
泛化仿函数(Generalized Functor)是一种将函数调用抽象为对象的技术,它允许我们将任何可调用实体(函数、成员函数、函数对象等)存储为对象,并在需要时进行调用。
2.1 类型列表(TypeList)的基础实现
类型列表是泛型编程中的重要技术,它允许我们在编译时操作类型集合。以下是简化实现:
cpp复制struct NullType {};
template <typename T, typename U>
struct TypeList {
typedef T Head;
typedef U Tail;
};
#define TYPELIST_1(T1) TypeList<T1, NullType>
#define TYPELIST_2(T1, T2) TypeList<T1, TYPELIST_1(T2)>
#define TYPELIST_3(T1, T2, T3) TypeList<T1, TYPELIST_2(T2, T3)>
这种技术使我们能够在编译时表示和操作函数的参数类型列表,为后续的仿函数实现奠定基础。
2.2 仿函数接口设计
仿函数的核心接口需要支持三种基本操作:
- 函数调用操作符重载
- 对象克隆(用于实现值语义)
- 多态销毁
cpp复制template <typename R>
class FunctorImpl<R, NullType> {
public:
virtual R operator()() = 0;
virtual FunctorImpl* Clone() const = 0;
virtual ~FunctorImpl() = 0;
};
template <typename R, typename T1>
class FunctorImpl<R, TYPELIST_1(T1)> {
public:
virtual R operator()(T1) = 0;
virtual FunctorImpl* Clone() const = 0;
virtual ~FunctorImpl() = 0;
};
3. 成员函数绑定的实现技术
将成员函数绑定到特定对象上是实现转发式命令的关键技术。这需要解决两个核心问题:
- 如何表示成员函数指针
- 如何将对象实例与成员函数绑定
3.1 成员函数指针的表示
在C++中,成员函数指针有其特殊的语法和语义:
cpp复制class MyClass {
public:
void MemberFunction(int);
};
// 成员函数指针类型声明
typedef void (MyClass::*MemberFuncPtr)(int);
// 使用示例
MyClass obj;
MemberFuncPtr ptr = &MyClass::MemberFunction;
(obj.*ptr)(42); // 调用成员函数
3.2 成员函数处理器实现
通过模板技术,我们可以创建一个通用的成员函数处理器:
cpp复制template <typename ParentFunctor, typename PointerToObj, typename PointerToMemFn>
class MemFuncHandler : public FunctorImpl<
typename ParentFunctor::ResultType,
typename ParentFunctor::ParamList> {
public:
typedef typename ParentFunctor::ResultType ResultType;
MemFuncHandler(const PointerToObj& pObj, PointerToMemFn pMemFn)
: pObj_(pObj), pMemFn_(pMemFn) {}
MemFuncHandler* Clone() const {
return new MemFuncHandler(*this);
}
ResultType operator()() {
return ((*pObj_).*pMemFn_)();
}
ResultType operator()(typename ParentFunctor::Param1 p1) {
return ((*pObj_).*pMemFn_)(p1);
}
private:
PointerToObj pObj_;
PointerToMemFn pMemFn_;
};
4. 参数绑定的高级技术
在实际应用中,我们经常需要将函数的某些参数预先绑定,创建新的函数对象。这就是所谓的柯里化(Currying)技术。
4.1 绑定第一个参数的实现
cpp复制template <typename Incoming>
class BinderFirst : public FunctorImpl<
typename Incoming::ResultType,
typename Incoming::Arguments::Tail> {
public:
typedef typename Incoming::ResultType ResultType;
typedef typename Incoming::Param1 Bound;
BinderFirst(const Incoming& fun, Bound bound)
: fun_(fun), bound_(bound) {}
BinderFirst* Clone() const {
return new BinderFirst(*this);
}
ResultType operator()() {
return fun_(bound_);
}
ResultType operator()(typename Outgoing::Param1 p1) {
return fun_(bound_, p1);
}
private:
Incoming fun_;
Bound bound_;
};
4.2 使用示例
cpp复制class Printer {
public:
void Print(const std::string& msg, int times) {
for (int i = 0; i < times; ++i) {
std::cout << msg << std::endl;
}
}
};
int main() {
Printer printer;
// 创建原始仿函数
Functor<void, TYPELIST_2(std::string, int)> printFunc(&printer, &Printer::Print);
// 绑定第一个参数
Functor<void, TYPELIST_1(int)> boundFunc = BindFirst(printFunc, "Hello");
// 调用绑定后的仿函数
boundFunc(3); // 相当于调用 printer.Print("Hello", 3)
}
5. 泛化仿函数的完整实现
将上述技术组合起来,我们可以构建一个完整的泛化仿函数框架:
5.1 仿函数外壳类
cpp复制template <typename ResultType, typename ParamList>
class Functor {
public:
typedef ResultType ResultType;
typedef ParamList ParamList;
// 默认构造函数
Functor() : pImpl_(nullptr) {}
// 成员函数构造函数
template <typename PtrToObj, typename PtrToMemFn>
Functor(PtrToObj pObj, PtrToMemFn pMemFn)
: pImpl_(new MemFuncHandler<Functor, PtrToObj, PtrToMemFn>(pObj, pMemFn)) {}
// 拷贝构造函数
Functor(const Functor& other)
: pImpl_(other.pImpl_ ? other.pImpl_->Clone() : nullptr) {}
// 析构函数
~Functor() { delete pImpl_; }
// 函数调用操作符
ResultType operator()() {
return (*pImpl_)();
}
template <typename P1>
ResultType operator()(P1 p1) {
return (*pImpl_)(p1);
}
private:
FunctorImpl<ResultType, ParamList>* pImpl_;
};
5.2 辅助绑定函数
cpp复制template <typename Functor>
Functor<typename Functor::ResultType, typename Functor::Arguments::Tail>
BindFirst(const Functor& fun, typename Functor::Param1 bound) {
typedef typename Functor::Arguments::Tail NewParamList;
typedef typename Functor::ResultType ResultType;
return Functor<ResultType, NewParamList>(
new BinderFirst<Functor>(fun, bound));
}
6. 实际应用案例分析
6.1 命令模式中的使用
cpp复制class Document {
public:
void Open() { /* 打开文档实现 */ }
void Save() { /* 保存文档实现 */ }
void Close() { /* 关闭文档实现 */ }
};
class MenuItem {
public:
MenuItem(const std::string& label, Functor<void, TYPELIST_0()> command)
: label_(label), command_(command) {}
void Click() {
command_();
}
private:
std::string label_;
Functor<void, TYPELIST_0()> command_;
};
int main() {
Document doc;
// 创建菜单项并绑定命令
MenuItem openItem("Open", Functor<void, TYPELIST_0()>(&doc, &Document::Open));
MenuItem saveItem("Save", Functor<void, TYPELIST_0()>(&doc, &Document::Save));
// 模拟菜单点击
openItem.Click();
saveItem.Click();
}
6.2 回调系统中的应用
cpp复制class Button {
public:
typedef Functor<void, TYPELIST_0()> ClickHandler;
void SetClickHandler(const ClickHandler& handler) {
handler_ = handler;
}
void SimulateClick() {
if (handler_) {
handler_();
}
}
private:
ClickHandler handler_;
};
class Dialog {
public:
void ShowMessage() {
std::cout << "Button clicked!" << std::endl;
}
};
int main() {
Button btn;
Dialog dlg;
// 设置按钮点击处理程序
btn.SetClickHandler(Functor<void, TYPELIST_0()>(&dlg, &Dialog::ShowMessage));
// 模拟按钮点击
btn.SimulateClick();
}
7. 性能优化与实现细节
7.1 小对象优化技术
为了避免频繁的内存分配,我们可以实现小对象优化:
cpp复制template <typename ResultType, typename ParamList>
class Functor {
// ... 其他成员 ...
// 小对象存储缓冲区
static const size_t BUFFER_SIZE = 32;
typedef char Buffer[BUFFER_SIZE];
// 使用placement new在缓冲区上创建对象
template <typename Impl>
void ConstructImpl(const Impl& impl) {
if (sizeof(Impl) <= BUFFER_SIZE) {
pImpl_ = new (buffer_) Impl(impl);
isOnHeap_ = false;
} else {
pImpl_ = new Impl(impl);
isOnHeap_ = true;
}
}
// 析构时需要区分存储位置
~Functor() {
if (pImpl_) {
if (isOnHeap_) {
delete pImpl_;
} else {
pImpl_->~FunctorImpl();
}
}
}
private:
FunctorImpl<ResultType, ParamList>* pImpl_;
Buffer buffer_;
bool isOnHeap_;
};
7.2 类型擦除与多态
泛化仿函数本质上是一种类型擦除技术,它通过多态接口将不同类型的可调用对象统一起来:
cpp复制// 普通函数适配器
template <typename R>
class FunctionHandler : public FunctorImpl<R, NullType> {
public:
typedef R (*FuncType)();
explicit FunctionHandler(FuncType func) : func_(func) {}
R operator()() override { return func_(); }
FunctionHandler* Clone() const override {
return new FunctionHandler(*this);
}
private:
FuncType func_;
};
// 函数对象适配器
template <typename F, typename R>
class FunctorHandler : public FunctorImpl<R, NullType> {
public:
explicit FunctorHandler(const F& f) : f_(f) {}
R operator()() override { return f_(); }
FunctorHandler* Clone() const override {
return new FunctorHandler(*this);
}
private:
F f_;
};
8. 现代C++中的替代方案
虽然这种技术在现代C++中仍然有效,但C++11及以后标准提供了更简洁的替代方案:
8.1 std::function与lambda表达式
cpp复制#include <functional>
class Document {
public:
void Save() { /* 保存实现 */ }
};
int main() {
Document doc;
// 使用std::function和lambda
std::function<void()> saveCommand = [&doc]() { doc.Save(); };
// 调用命令
saveCommand();
}
8.2 std::bind与占位符
cpp复制#include <functional>
using namespace std::placeholders;
class Printer {
public:
void Print(const std::string& msg, int times) {
for (int i = 0; i < times; ++i) {
std::cout << msg << std::endl;
}
}
};
int main() {
Printer printer;
// 绑定第一个参数
auto printHello = std::bind(&Printer::Print, &printer, "Hello", _1);
// 调用
printHello(3); // 打印"Hello"三次
}
8.3 传统实现与现代实现的对比
| 特性 | 传统泛化仿函数实现 | 现代C++实现 |
|---|---|---|
| 类型安全 | 是 | 是 |
| 成员函数绑定 | 支持 | 支持 |
| 参数绑定 | 需要手动实现 | std::bind |
| 语法简洁性 | 复杂 | 简洁 |
| 性能 | 通常较高 | 可能稍低 |
| 代码可维护性 | 较低 | 较高 |
| C++标准要求 | C++98 | C++11 |
在实际项目中,除非有特殊的兼容性要求或性能考量,否则推荐使用现代C++提供的标准库组件。然而,理解这些底层实现原理对于深入掌握C++泛型编程和设计模式仍然具有重要意义。