C++中包装器的使用示例
在 C++ 中,包装器(Wrapper) 也常被称为适配器(Adapter),是一种设计模式(结构型模式)的实现——核心作用是包装一个已有对象(或函数、指针等),转换其接口、隐藏实现细节,或增强其功能,让原本不兼容的接口能够协同工作,同时不改变原对象的逻辑。
包装器的核心价值:解耦、复用、接口统一。它就像一个“翻译官”,让两个原本无法直接沟通的组件(如不同接口的类、不同格式的函数)能够正常交互。
一、C++ 中常见的包装器类型
C++ 标准库提供了多种内置包装器,同时也支持自定义包装器。按包装对象的不同,主要分为以下几类:
1. 函数包装器(std::function)
最常用的包装器,用于包装任意可调用对象(函数指针、lambda 表达式、函数对象、类成员函数等),统一其调用接口。
核心用途:
- 消除可调用对象的类型差异,实现“类型擦除”(例如,不同签名的函数统一用
std::function存储); - 作为回调函数的统一载体(如 GUI 事件回调、异步任务回调)。
语法与示例:
#include <functional>
#include <iostream>
// 普通函数
int add(int a, int b) { return a + b; }
// 函数对象(仿函数)
struct Multiply {
int operator()(int a, int b) { return a * b; }
};
// 类成员函数
class Calculator {
public:
int subtract(int a, int b) { return a - b; }
};
int main() {
// 包装普通函数
std::function<int(int, int)> func1 = add;
std::cout << func1(2, 3) << std::endl; // 输出 5
// 包装函数对象
std::function<int(int, int)> func2 = Multiply{};
std::cout << func2(2, 3) << std::endl; // 输出 6
// 包装 lambda 表达式
std::function<int(int, int)> func3 = [](int a, int b) { return a / b; };
std::cout << func3(6, 2) << std::endl; // 输出 3
// 包装类成员函数(需绑定实例)
Calculator calc;
std::function<int(int, int)> func4 = std::bind(&Calculator::subtract, &calc, std::placeholders::_1, std::placeholders::_2);
std::cout << func4(5, 2) << std::endl; // 输出 3
return 0;
}
关键说明:
std::function的模板参数是函数签名(返回值类型 + 参数类型列表);- 支持拷贝、赋值,可作为容器元素(如
std::vector<std::function<int(int)>>); - 空的
std::function调用会抛出std::bad_function_call异常。
2. 指针包装器(智能指针:std::unique_ptr/std::shared_ptr)
智能指针是裸指针的包装器,核心作用是自动管理内存(避免内存泄漏),同时提供与裸指针兼容的接口(如 operator*、operator->)。
核心用途:
- 替代裸指针,实现“资源自动释放”(RAII 机制);
- 隐藏内存管理细节(如引用计数、独占所有权)。
示例:
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass 构造" << std::endl; }
~MyClass() { std::cout << "MyClass 析构" << std::endl; }
void show() { std::cout << "Hello MyClass" << std::endl; }
};
int main() {
// std::unique_ptr:独占所有权
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
ptr1->show(); // 接口与裸指针一致
// std::shared_ptr:共享所有权(引用计数)
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr3 = ptr2; // 引用计数+1
ptr3->show();
// 无需手动 delete,作用域结束时自动析构
return 0;
}
关键说明:
- 智能指针是“资源包装器”,遵循 RAII(资源获取即初始化)原则;
- 包装了裸指针的“内存管理”逻辑,但对外提供与裸指针兼容的接口,不改变原类的使用方式。
3. 迭代器适配器(std::reverse_iterator/std::insert_iterator等)
迭代器适配器是原生迭代器的包装器,用于转换迭代器的行为(如反向遍历、插入元素而非覆盖),让同一容器支持不同的遍历/操作方式。
常见类型:
std::reverse_iterator:反向迭代器(包装正向迭代器,实现反向遍历);std::insert_iterator:插入迭代器(包装容器迭代器,实现“插入”而非“覆盖”赋值);std::back_insert_iterator/std::front_insert_iterator:尾部/头部插入迭代器。
示例(反向迭代器):
#include <vector>
#include <iostream>
#include <iterator> // 包含迭代器适配器
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 正向遍历
std::cout << "正向遍历:";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " "; // 输出 1 2 3 4 5
}
std::cout << std::endl;
// 反向遍历(使用 reverse_iterator 包装 begin()/end())
std::cout << "反向遍历:";
for (auto it = std::reverse_iterator(vec.end()); it != std::reverse_iterator(vec.begin()); ++it) {
std::cout << *it << " "; // 输出 5 4 3 2 1
}
std::cout << std::endl;
return 0;
}
关键说明:
- 迭代器适配器不改变容器的存储结构,仅包装迭代器的
operator++/operator*等行为; - 对外提供统一的迭代器接口(如
++、*),用户无需关心底层实现差异。
4. 函数适配器(std::bind/std::not_fn)
函数适配器用于包装可调用对象,修改其参数列表或逻辑(如绑定参数、取反逻辑),生成新的可调用对象。
(1)std::bind:参数绑定适配器
核心作用:绑定可调用对象的部分参数,生成参数更少的新函数。
示例:
#include <functional>
#include <iostream>
int add(int a, int b, int c) { return a + b + c; }
int main() {
// 绑定第一个参数为 10,生成新函数:int(int, int)
auto add10 = std::bind(add, 10, std::placeholders::_1, std::placeholders::_2);
std::cout << add10(2, 3) << std::endl; // 10+2+3=15
// 绑定后两个参数为 5、6,生成新函数:int(int)
auto add56 = std::bind(add, std::placeholders::_1, 5, 6);
std::cout << add56(4) << std::endl; // 4+5+6=15
return 0;
}
(2)std::not_fn:逻辑取反适配器
核心作用:包装返回布尔值的可调用对象,生成“逻辑取反”的新函数(C++17 引入)。
示例:
#include <functional>
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 查找偶数(原逻辑:x % 2 == 0)
auto even = [](int x) { return x % 2 == 0; };
auto it1 = std::find_if(vec.begin(), vec.end(), even);
std::cout << "第一个偶数:" << *it1 << std::endl; // 输出 2
// 查找奇数(包装 even,逻辑取反)
auto odd = std::not_fn(even);
auto it2 = std::find_if(vec.begin(), vec.end(), odd);
std::cout << "第一个奇数:" << *it2 << std::endl; // 输出 1
return 0;
}
5. 自定义包装器(类适配器)
除了标准库提供的包装器,也可以手动实现类级别的包装器,用于适配不同接口的类。
场景:
假设已有一个旧接口的类 OldInterface,新代码需要使用统一的 NewInterface 接口,此时可通过包装器适配。
示例:
#include <iostream>
#include <string>
// 旧接口(无法修改)
class OldInterface {
public:
void do_old_task(const std::string& msg) {
std::cout << "旧接口执行任务:" << msg << std::endl;
}
};
// 新接口(统一规范)
class NewInterface {
public:
virtual ~NewInterface() = default;
virtual void execute(const std::string& task) = 0;
};
// 包装器(适配器):将 OldInterface 适配为 NewInterface
class OldToNewAdapter : public NewInterface {
private:
OldInterface old_obj; // 包装旧接口对象
public:
void execute(const std::string& task) override {
// 转换接口:新接口的 execute 调用旧接口的 do_old_task
old_obj.do_old_task("适配后:" + task);
}
};
// 新代码使用统一接口
void use_new_interface(NewInterface& obj) {
obj.execute("完成数据处理");
}
int main() {
OldToNewAdapter adapter;
use_new_interface(adapter); // 输出:旧接口执行任务:适配后:完成数据处理
return 0;
}
关键说明:
- 自定义包装器通常采用“组合”(包装被适配对象)或“继承”(适配接口)的方式;
- 核心是“接口转换”:对外提供目标接口,对内调用被包装对象的原有接口。
二、包装器的核心特性
- 接口转换:将被包装对象的接口转换为目标接口(如
OldInterface→NewInterface); - 透明性:用户无需关心被包装对象的细节,仅需使用包装器提供的统一接口;
- 增强功能:可在包装器中添加额外逻辑(如日志、缓存、权限校验),不修改原对象;
- 复用性:同一包装器可适配多个同类被包装对象(如
std::function可包装任意符合签名的可调用对象)。
三、包装器与装饰器的区别(易混淆点)
很多人会将包装器(适配器)与装饰器(Decorator)混淆,两者核心差异在于目的不同:
- 「包装器(适配器)」:核心是接口适配(让不兼容的接口协同工作);
- 「装饰器」:核心是功能增强(在不改变接口的前提下,动态添加新功能)。
示例区别:
- 适配器:将
OldInterface::do_old_task适配为NewInterface::execute(接口变了); - 装饰器:给
NewInterface::execute增加日志功能(接口不变,功能变了)。
总结
C++ 中的包装器(适配器)是“接口转换与复用”的核心工具,分为:
- 标准库内置包装器:
std::function(函数)、智能指针(指针)、迭代器适配器(迭代器)、std::bind(函数参数); - 自定义包装器:类级别的接口适配。
其核心价值是解耦(隔离不同接口的组件)、复用(无需修改原有代码)、统一接口(降低使用成本),是 C++ 中实现灵活设计的重要手段。
到此这篇关于C++中包装器的使用示例的文章就介绍到这了,更多相关C++ 包装器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
文件编译时出现multiple definition of ''xxxxxx''的具体解决方法
以下是对文件编译时出现multiple definition of 'xxxxxx'的解决方法进行了详细的分析介绍,如也遇到此问题的朋友们可以过来参考下2013-07-07


最新评论