跳转到内容

C++/functional

维基教科书,自由的教学读本
< C++

functional是b:C++标准程式库中的一个b:头文件,主要用于C++ STL标准中提供 “函数对象 / 可调用对象”相关的工具。它的核心用途可以概括为:把普通函数、成员函数、lambda、函数对象等统一当作“可调用对象”来保存、传递、包装、绑定和调用。

命名空间placeholders

[编辑]

定义了用作 std::bind 表达式中的未绑定实参的占位符常量:_1, _2, _3, _4, ...

[编辑]

function类模板

[编辑]

std::function 是一个通用函数包装器,可以保存任何签名(signature)兼容的可调用对象。

必要性:如果lambda表达式赋值给一个函数指针,则编译报错。这就需要一个能保存各种可调用类型对象的办法。实际上,lambda表达式有可能捕获外部对象及其值做成闭包,因此每个lambda表达式都是独一无二的类型。

std::function是一个可变参类模板,是一个通用的函数包装器(Polymorphic function wrapper)。std::function的实例可以存储、复制和调用任何可复制构造的可调用目标,包括普通函数、成员函数、类对象(重载了operator()的类的对象)、lambda表达式等。是对C++现有的可调用实体的一种类型安全的包裹(相比而言,函数指针这种可调用实体,是类型不安全的)。

std::function中存储的可调用对象被称之为std::function的目标。若std::function中不含目标,调用不含目标的std::function会抛出std::bad_function_call 异常。

#include <functional>
#include <iostream>

// 定义一个类,用function包裹其成员
struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};

// 定义一个普通函数,用function进行包裹
void print_num(int i)
{
    std::cout << i << '\n';
}

// 定义一个重载了operator()的类,function可以包裹其对象
struct PrintNum {
    void operator()(int i) const {
        std::cout << i << '\n';
    }
};
 
int main()
{
    // 存储自由函数
    std::function<void(int)> f_display = print_num;
    f_display(-9);
 
    // 存储 lambda
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();
 
    // 存储到 std::bind 调用的结果
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();
 
    // 存储到成员函数的调用,第一个入参为this指针
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    // 第一个入参为this指针
    f_add_display(foo, 1);
    f_add_display(314159, 1);
 
    // 存储到数据成员访问器的调用
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';
 
    // 存储到成员函数及对象的调用
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
    f_add_display2(2);
 
    // 存储到成员函数和对象指针的调用
    std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
    f_add_display3(3);
 
    // 存储到函数对象的调用
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);
 
    auto factorial = [](int n) {
        // 存储 lambda 对象以模拟“递归 lambda ”,注意额外开销
        std::function<int(int)> fac = [&](int n){ return (n < 2) ? 1 : n*fac(n-1); };
        // note that "auto fac = [&](int n){...};" does not work in recursive calls
        return fac(n);
    };
    for (int i{5}; i != 8; ++i) { std::cout << i << "! = " << factorial(i) << ";  "; }
}

std::function底层核心原理:针对每一种可调用类型,标准库都会生成一套专属封装子类callable,子类内部存储原可调用对象、并实现调用逻辑;所有封装子类公有继承同一个抽象基类接口callable_base,外层std::function仅持有该基类指针。

强制标准:若待存储的可调用对象是普通函数指针或std::reference_wrapper引用包装器,std::function实现禁止额外堆内存分配。

各编译器 SBO 缓冲区大小(64 位平台):

  • GCC / Clang:16 字节
  • MSVC:32 字节

MSVC的std::function实现

[编辑]

MSVC的std::function底层依然基于虚表多态实现类型擦除,但做了两大关键工程优化:

  • 内置32字节栈内小对象缓冲区SBO(64位环境),优先无堆分配;
  • 用union复用内存:小对象直接存缓冲区,大对象存堆指针;

严格遵循标准强制规则:

  • 函数指针/reference_wrapper永远不走堆、构造noexcept
  • 完整值语义:拷贝深克隆内部可调用实体,移动仅转移资源、无新分配。

64位下sizeof(std::function<void()>) == 40,构成:

  • 8字节:虚表指针_Vtable(类型擦除调度入口)
  • 32字节:SBO 栈内缓冲区联合体 _Storage

合计40字节,固定尺寸,和标准要求一致。

类型擦除后的抽象统一接口虚基类_Func_base。所有被包装可调用对象统一继承该抽象基类,仅暴露 4 个虚函数:

struct _Func_base {
    virtual ~_Func_base() noexcept = default;
    // 执行调用
    virtual auto _Invoke(...) const -> Ret = 0;
    // 克隆一份内部可调用(拷贝构造用,返回堆上新base)
    virtual _Func_base* _Clone() const = 0;
    // 判断是否为空目标
    virtual bool _Empty() const noexcept = 0;
    // 自定义分配器支持的克隆(带Allocator)
    virtual _Func_base* _Clone_alloc(_Alloc&) const = 0;
};

所有动态分发(调用、拷贝、判空)全部走这套虚函数接口,对外彻底抹除底层可调用类型 F,即类型擦除。

_Func_impl<F, Alloc>:保存,并实现虚函数 模板派生封装类 _Func_impl<F, Alloc, R, Args...>对每一种传入的可调用类型 F,编译器实例化一份独立派生类:内部直接持有完整的、具体 callable对象F;实现调用、拷贝、判空、分配器等全部4个虚函数:把调用操作转发给底层F;_Clone()新建堆上_Func_impl<F>,完成深拷贝;若传入自定义分配器,使用传入 Alloc 完成堆内存分配 / 释放。

std::function<R(Args...)>:对外的值语义包装器 / facade

这种三层_Base / _Impl / _Manager class结构,是很常见的Type Erasure设计模式,或者称Concept-Model-Wrapper Idiom。

mem_fn函数模板

[编辑]

从成员指针创建出函数对象。对数据成员指针也适用。与bind函数模板的区别是,mem_fn只关注、包装了类成员指针,而bind需要指明参数。例如:

#include <functional>
#include <iostream>
 
struct Foo {
    void display_greeting() {
        std::cout << "Hello, world.\n";
    }
    void display_number(int i) {
        std::cout << "number: " << i << '\n';
    }
    int data = 7;
};
 
int main() {
    Foo f;
 
    auto greet = std::mem_fn(&Foo::display_greeting);
    greet(&f);
 
    auto print_num = std::mem_fn(&Foo::display_number);
    print_num(&f, 42);
 
    auto access_data = std::mem_fn(&Foo::data);
    std::cout << "data: " << access_data(&f) << '\n';
}

bad_function_call类

[编辑]

调用空的 std::function 时抛出的异常

is_bind_expression类模板

[编辑]

若类型T是调用 std::bind 产生的类型,则此模板从 std::true_type 导出。对于任何其他类型,此模板从 std::false_type 导出。

is_placeholder类模板

[编辑]

若类型T是标准占位符_1 、 _2 、 _3、……的类型,则此模板分别派生自std::integral_constant<int,1> 、 std::integral_constant<int,2> 、 std::integral_constant<int,3> 等。

若类型T不是标准占位符类型,则此模板派生自std::integral_constant<int,0>。

实际上,bind函数模板用is_placeholder来确定是第几个参数的占位符。

reference_wrapper类模板

[编辑]

可复制构造 (Copy Constructible) 且可复制赋值 (Copy Assignable) 的引用包装器(引用的容器,引用代理类模板)。内部实现,有一个数据类型的指针成员变量,在需要数据类型引用时返回相应的解引用。

配套快捷工厂函数:

  • std::ref(x) → reference_wrapper<T>(普通左值引用)
  • std::cref(x) → reference_wrapper<const T>(常量引用)

核心痛点(普通引用 T& 的缺陷),普通引用不能拷贝、不能赋值;

  • STL 容器(vector/list)不允许存放 T&;
  • std::bindstd::threadstd::function 默认会拷贝参数,直接传变量会复制,无法实现引用传递。

reference_wrapper核心特性:

  • 可拷贝、可赋值
int a = 10;
std::reference_wrapper<int> r1 = a;
std::reference_wrapper<int> r2 = r1; // 允许拷贝
  • 隐式转换为 T&,可以直接传给接收 T& 的函数,无需手动解包:
void func(int& x) { x *= 2; }
int n = 5;
auto r = std::ref(n);
func(r); // 自动转 int&,n 变成 10
  • 获取原对象 .get()
int val = r.get();
r.get() = 99; // 修改原变量
  • 可调用:包装函数时支持 operator()
void foo() {}
auto fwrap = std::ref(foo);
fwrap(); // 等价 foo()
  • 不延长生命周期:仅存指针,若原对象销毁,包装器会产生悬空引用,和裸指针风险一致。

使用场景1:容器里存放 “引用”(vector 不能存 T&)

#include <vector>
#include <functional>
#include <iostream>

int main() {
    int a = 1, b = 2, c = 3;
    std::vector<std::reference_wrapper<int>> vec{std::ref(a), std::ref(b), std::ref(c)};

    for (auto r : vec) {
        r.get() *= 10;
    }
    std::cout << a << " " << b << " " << c; // 10 20 30
    return 0;
}

使用场景 2:std::bind /std::function 传递引用(最常用),bind 默认拷贝参数,不加 std::ref 只会复制副本,外部修改无法同步:

#include <functional>
#include <iostream>

void add(int& x, int y) { x += y; }

int main() {
    int num = 10;
    // 错误:拷贝 num,内部修改不影响外部
    auto bad = std::bind(add, num, 5);
    bad();
    std::cout << num << "\n"; // 10

    // 正确:reference_wrapper 传递真实引用
    auto good = std::bind(add, std::ref(num), 5);
    good();
    std::cout << num << "\n"; // 15
    return 0;
}

使用场景 3:std::thread线程传参避免拷贝,thread构造参数一律拷贝,大量数据用std::ref减少复制开销:

#include <thread>
#include <functional>
#include <iostream>

void work(std::string& s) { s += " done"; }

int main() {
    std::string msg = "task";
    std::thread t(work, std::ref(msg));
    t.join();
    std::cout << msg; // task done
    return 0;
}

hash类模板

[编辑]
template< class Key > struct hash;

允许的特化模板类是“函数对象”,实现了哈希函数。即定义了operator() const,接受一个Key类型的参数,返回size_t的哈希结果值。对于一个类型,相同值具有相同的哈希结果,不同值具有不同的哈希结果(受限于size_t的值域)。该类别用于4种无序关联容器(哈希容器),不适用于加密算法。

各种基础类型、大多数标准库的类型(如std::string)已经特化实现了hash类模板。但对于std::pair,需要用boost::hash

对自定义的类实现hash功能,有两种办法:

#include <cstddef>
#include <functional>
#include <iomanip>
#include <iostream>
#include <string>
#include <unordered_set>
 
struct S
{
    std::string first_name;
    std::string last_name;
    bool operator==(const S&) const = default; // since C++20
};
 
// Before C++20.
// bool operator==(const S& lhs, const S& rhs)
// {
//     return lhs.first_name == rhs.first_name && lhs.last_name == rhs.last_name;
// }
 
// Custom hash can be a standalone function object.
struct MyHash
{
    std::size_t operator()(const S& s) const noexcept
    {
        std::size_t h1 = std::hash<std::string>{}(s.first_name);
        std::size_t h2 = std::hash<std::string>{}(s.last_name);
        return h1 ^ (h2 << 1); // or use boost::hash_combine
    }
};
 
// Custom specialization of std::hash can be injected in namespace std.
template<>
struct std::hash<S>
{
    std::size_t operator()(const S& s) const noexcept
    {
        std::size_t h1 = std::hash<std::string>{}(s.first_name);
        std::size_t h2 = std::hash<std::string>{}(s.last_name);
        return h1 ^ (h2 << 1); 
        // or use boost::hash_combine:
        // std::size_t seed = 0;
        // boost::hash_combine(seed, s.first_name);
        // boost::hash_combine(seed, s.last_name);
    }
};

//推荐用法
	template <class T> class A {
		T x;
	public:
		A(T x) : x(x) {}
		bool operator==(A const& b) { return x == b.x; }            //可定义为成员或普通函数
		//std::size_t hash_value() { return boost::hash<T>()(x); }  //error不能定义为类成员函数
		friend std::size_t hash_value(const A<T>& a) { return boost::hash<T>{}(a.x); }
	};

//备选用法-编译器若不支持模板friend(不支持 ADL 的编译器)
	// (不能声明为friend hash_value)hash_value需要在boost命名空间中定义
	template <class T> class A1 {
		T x;
	public:
		A1(T x) : x(x) {}
		bool operator==(A1 const& b) { return x == b.x; }//可定义为成员或普通函数
		std::size_t hash() const {return boost::hash<T>{}(x); }
	};

template <class T>std::size_t hash_value(A1<T> x){return x.hash();}
 
int main()
{
    std::string str = "Meet the new boss...";
    std::size_t str_hash = std::hash<std::string>{}(str);
    std::cout << "hash(" << std::quoted(str) << ") =\t" << str_hash << '\n';
 
    S obj = {"Hubert", "Farnsworth"};
    // Using the standalone function object.
    std::cout << "hash(" << std::quoted(obj.first_name) << ", "
              << std::quoted(obj.last_name) << ") =\t"
              << MyHash{}(obj) << " (using MyHash) or\n\t\t\t\t"
              << std::hash<S>{}(obj) << " (using injected specialization)\n";
 
    // Custom hash makes it possible to use custom types in unordered containers.
    // The example will use the injected std::hash<S> specialization above,
    // to use MyHash instead, pass it as a second template argument.
    std::unordered_set<S> names = {obj, {"Bender", "Rodriguez"}, {"Turanga", "Leela"}};
    for (auto const& s: names)
        std::cout << std::quoted(s.first_name) << ' '
                  << std::quoted(s.last_name) << '\n';
}

函数

[编辑]

bind函数模板

[编辑]

std::bind 用来把函数的一部分参数提前绑定,生成一个新的可调用对象。

#include <functional>
#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    //把 add 的第一个参数固定为 10,第二个参数留给以后传入:
    auto add10 = std::bind(add, 10, std::placeholders::_1);

    std::cout << add10(5) << std::endl;  // 15
}

现代 C++ 中更推荐lambda代替std::bind,更为清晰、更直观、类型推导更清楚、错误信息更友好、可读性更高。例如:

auto add10 = [](int x) {
    return add(10, x);
};

ref与cref函数模板

[编辑]

创建具有从其实参推导的类型的 std::reference_wrapper

invoke函数模板

[编辑]

(C++17)以给定实参调用任意可调用 (Callable) 对象

函数对象

[编辑]

<functional> 中定义了很多标准函数对象类模板

算术运算类模板

[编辑]
  • std::plus
  • std::minus
  • std::multiplies
  • std::divides
  • std::modulus
  • std::negate

例如:

#include <functional>
#include <iostream>

int main() {
    std::plus<int> add;

    std::cout << add(2, 3) << std::endl;  // 5
}

比较运算类模板

[编辑]
  • std::equal_to
  • std::not_equal_to
  • std::greater
  • std::less
  • std::greater_equal
  • std::less_equal

例如定义大根堆/小根堆:

#include <queue>
#include <vector>
#include <functional>

std::priority_queue<int, std::vector<int>, std::less<int>> maxHeap;
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

与C++20 ranges 体系中的受concepts约束的比较函数对象

[编辑]
  • ranges::equal_to类
  • ranges::not_equal_to类
  • ranges::greater类
  • ranges::less类
  • ranges::greater_equal类
  • ranges::less_equal类

std::ranges::equal_to 更严格、更“概念化”,要求参与比较的类型满足equality_comparable_with这个concept约束;而std::equal_to<> 更传统、更宽松,基本上只要 a == b 表达式可用就可以。

std::equal_tostd::ranges::equal_to比较为例
项目 std::equal_to std::ranges::equal_to
所属头文件 <functional> <functional>
所属命名空间 std std::ranges
出现时间 C++98;std::equal_to<> 透明版本为 C++14 C++20
是否模板 是。常见形式为 std::equal_to<T>std::equal_to<>(透明版本) 通常作为非模板函数对象类型使用,例如 std::ranges::equal_to{}
是否使用 concepts 约束 否。属于传统函数对象,主要依赖表达式是否可用 是。使用 C++20 concepts 约束
主要用途 传统 STL、容器、算法、哈希容器 Ranges 算法、C++20 泛型代码
是否支持异构比较 std::equal_to<>(透明版本)支持 支持,但要求类型满足相应的比较 concept,例如 equality_comparable_with
语义要求 较宽松,通常只要 a == b 表达式可用即可 更严格,要求比较关系更完整、对称,并满足相关 concept

逻辑运算类模板

[编辑]
  • std::logical_and
  • std::logical_or
  • std::logical_not

位运算类模板

[编辑]
  • bit_and类模板
  • bit_or类模板
  • bit_xor类模板
  • bit_not类模板

取反器

[编辑]

not_fn函数模板:(C++17)创建返回其保有的函数对象的结果之补的函数对象

搜索器

[编辑]
  • default_searcher类模板:(C++17)标准 C++ 库搜索算法实现
  • boyer_moore_searcher类模板:(C++17)Boyer-Moore 搜索算法实现
  • boyer_moore_horspool_searcher类模板:(C++17)Boyer-Moore-Horspool 搜索算法实现

示例

[编辑]
#include <functional>
#include <iostream>
using namespace std;

std::function< int(int)> Functional;

// 普通函数
int TestFunc(int a)
{
	return a;
}

// Lambda表达式
auto lambda = [](int a)->int { return a; };

// 仿函数(functor)
class Functor
{
public:
	int operator()(int a)
	{
		return a;
	}
};

// 1.类成员函数
// 2.类静态函数
class TestClass
{
public:
	int ClassMember(int a) { return a; }
	static int StaticMember(int a) { return a; }
};

int main()
{
	// 普通函数
	Functional = TestFunc;
	int result = Functional(10);
	cout << "普通函数:" << result << endl;

	// Lambda表达式
	Functional = lambda;
	result = Functional(20);
	cout << "Lambda表达式:" << result << endl;

	// 仿函数
	Functor testFunctor;
	Functional = testFunctor;
	result = Functional(30);
	cout << "仿函数:" << result << endl;

	// 类成员函数
	TestClass testObj;
	Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
	result = Functional(40);
	cout << "类成员函数:" << result << endl;

	// 类静态函数
	Functional = TestClass::StaticMember;
	result = Functional(50);
	cout << "类静态函数:" << result << endl;

	return 0;
}