std::move_only_function是C++23引入的通用函数包装器,它和std::function的核心区别在于不支持拷贝操作,仅支持移动操作,因此非常适合包装那些不可拷贝但可移动的可调用对象,比如包含unique_ptr成员的任务对象、lambda表达式捕获了不可拷贝资源的场景等。

std::move_only_function的基本特性
std::move_only_function定义在<functional>头文件中,它的模板参数和std::function类似,用于指定包装的可调用对象的签名。和std::function相比,它删除了拷贝构造函数和拷贝赋值运算符,只保留移动相关的操作,这也意味着它只能包装那些满足可移动要求的可调用对象。
我们可以通过以下简单示例感受它的基本用法:
#include <functional>
#include <iostream>
#include <memory>
int main() {
// 定义一个捕获了unique_ptr的lambda,该lambda不可拷贝
auto lambda = [ptr = std::make_unique<int>(10)]() mutable {
std::cout << *ptr << std::endl;
};
// 用std::move_only_function包装该lambda
std::move_only_function<void()> func = std::move(lambda);
// 调用包装后的可调用对象
func();
return 0;
}
处理非拷贝任务的核心优势
在实际开发中,很多可调用对象本身是不可拷贝的,比如内部持有std::unique_ptr、std::ifstream等独占资源的对象,或者某些自定义的不支持拷贝的类型。如果使用std::function来包装这类对象,编译阶段就会报错,因为std::function要求包装的对象必须可拷贝。
而std::move_only_function没有这个限制,只要可调用对象支持移动操作就可以被包装。以下是两种包装器的对比:
| 特性 | std::function | std::move_only_function |
|---|---|---|
| 拷贝支持 | 支持 | 不支持 |
| 移动支持 | 支持 | 支持 |
| 包装不可拷贝对象 | 不支持 | 支持 |
| 内存开销 | 可能更高(需要支持拷贝的小对象优化) | 相对更低 |
实际应用场景示例
场景1:包装包含独占资源的任务
当任务对象内部持有独占资源时,std::move_only_function可以完美适配:
#include <functional>
#include <iostream>
#include <memory>
#include <vector>
// 不可拷贝的任务类,内部持有unique_ptr
class NonCopyableTask {
public:
NonCopyableTask(std::unique_ptr<int> data) : data_(std::move(data)) {}
// 删除拷贝构造和拷贝赋值
NonCopyableTask(const NonCopyableTask&) = delete;
NonCopyableTask& operator=(const NonCopyableTask&) = delete;
// 支持移动
NonCopyableTask(NonCopyableTask&&) = default;
NonCopyableTask& operator=(NonCopyableTask&&) = default;
void operator()() const {
if (data_) {
std::cout << "Task data: " << *data_ << std::endl;
}
}
private:
std::unique_ptr<int> data_;
};
int main() {
std::vector<std::move_only_function<void()>> tasks;
// 创建不可拷贝的任务对象,移动到vector中
tasks.emplace_back(NonCopyableTask(std::make_unique<int>(20)));
tasks.emplace_back(NonCopyableTask(std::make_unique<int>(30)));
// 执行所有任务
for (auto& task : tasks) {
task();
}
return 0;
}
场景2:异步任务队列中的使用
在异步任务队列中,很多任务可能包含不可拷贝的资源,使用std::move_only_function作为队列的元素类型,可以避免不必要的拷贝,同时支持传递不可拷贝的任务:
#include <functional>
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory>
// 简单的线程安全任务队列,存储不可拷贝的任务
class TaskQueue {
public:
void push(std::move_only_function<void()> task) {
std::lock_guard<std::mutex> lock(mtx_);
tasks_.push(std::move(task));
cv_.notify_one();
}
std::move_only_function<void()> pop() {
std::unique_lock<std::mutex> lock(mtx_);
cv_.wait(lock, [this]() { return !tasks_.empty(); });
auto task = std::move(tasks_.front());
tasks_.pop();
return task;
}
private:
std::queue<std::move_only_function<void()>> tasks_;
std::mutex mtx_;
std::condition_variable cv_;
};
int main() {
TaskQueue queue;
// 启动工作线程
std::thread worker([&queue]() {
while (true) {
auto task = queue.pop();
task();
}
});
// 提交包含unique_ptr的任务
queue.push([ptr = std::make_unique<std::string>("Hello from task")]() {
std::cout << *ptr << std::endl;
});
// 等待任务执行(实际场景中需要更优雅的退出机制)
std::this_thread::sleep_for(std::chrono::seconds(1));
worker.detach();
return 0;
}
使用注意事项
- std::move_only_function不支持拷贝,因此不能直接用拷贝的方式传递它,只能通过std::move进行移动传递。
- 当std::move_only_function对象为空时(即没有包装任何可调用对象),调用它会抛出std::bad_function_call异常,使用前可以通过operator bool()检查是否为空。
- 它和std::function不互通,不能把std::move_only_function直接赋值给std::function,反过来也不行,两者的设计目标不同。
- 如果可调用对象既需要支持拷贝又需要支持移动,优先使用std::function;只有当明确只需要移动、不需要拷贝的场景,才选择std::move_only_function。
总结
std::move_only_function是C++23为解决不可拷贝可调用对象包装问题推出的新特性,它填补了std::function无法处理非拷贝任务的空白。通过仅支持移动操作的特性,它可以在包装独占资源任务、异步任务队列等场景中发挥重要作用,同时相比std::function可能有更低的内存开销。开发者在实际使用时,需要根据任务是否可拷贝来选择合适的函数包装器,充分发挥两者的优势。
std::move_only_functionC++23非拷贝任务移动语义修改时间:2026-07-17 03:06:35