关于C++的函数指针以及它在C++11中的变化
文章目录
在对函数指针赋值时,就结果而言,加或不加
&
的结果是一样的。比如:
void fcn();
void (*fp1) = fcn; // 两者在vscode上的结果一致
void (*fp2) = &fcn;
1. 可调用对象(Callable Objects)
可调用对象有如下几种:
-
函数指针
// 1.函数指针 void add(int a, int b) { cout << "a + b = " << a + b; return; } void (*func)(int, int) = add; // 函数指针赋值 func(1, 2); // 函数指针调用
-
类成员(函数)指针:非静态成员函数指针前必须加上调用类的作用域
class_name::
,但是静态成员函数指针不需要。void (*func)(int, int); // 静态成员函数指针、函数指针 void (class_name::*func)(int, int); // 非静态成员函数指针
// 4.类成员函数指针 class Object { public: // 静态成员函数 static void add_static(int a, int b) { cout << "a + b = " << a + b; return; } // 非静态成员函数 void add(int a, int b) { cout << "a + b = " << a + b; return; } } // (1) 静态成员函数 void (*func)(int, int) = Object::add; // 与一般函数的赋值相同 func(1, 2); // (2) 非静态成员函数 void (Object::*func)(int, int) = Object::add; // 非静态类成员函数指针赋值 // 通过对象调用 Object obj; (obj.*func)(1, 2); // 非静态类成员函数指针调用(必须加括号) // 通过对象指针调用 Object* obj = new Object; (obj->*func)(1, 2); // 非静态类成员函数指针调用(必须加括号)
-
具有operator()成员函数的类对象(仿函数)
// 2.仿函数 struct Add { void operator() (int a, int b) { cout << "a + b = " << a + b; return; } }; Add add; // 实例化的类对象(object), 即仿函数 add(3, 4); // 仿函数调用
-
可被转换为函数指针的类对象
以上涉及的对象可以像一个函数一样做调用操作,统称为可调用对象。现在,C++11通过提供std::function
和std::bind
统一了可调用对象的各种操作。
2. std::function
以上可见,C++中的可调用对象的定义方法有很多种。为了统一泛化函数对象、函数指针、引用函数、成员函数的指针的各种操作,为了统一形式,C++11推出了std::function
。
std::function
是可调用对象的包装器。它是一个类模板,可以包装:函数指针、类成员函数指针或任意类型的函数对象。以上使用的可调用对象都可以使用std::function<void(int, int)>
类型来包装。
#include <functional>
typedef std::function<void(int, int)> Fcn; // 函数封装类型
2.1.封装一般函数
// 函数
void add1(int a, int b) {
cout << "a + b = " << a + b;
return;
}
Fcn func1 = add1; // 封装
func1(1, 2); // 调用
2.2.封装类成员函数
// 类成员函数指针
class Object {
public:
// 静态成员函数
static void add_static(int a, int b) {
cout << "a + b = " << a + b;
return;
}
// 非静态成员函数 (参数列表中有隐藏的this指针)
void add(int a, int b) {
cout << "a + b = " << a + b;
return;
}
}
2.2.1.静态成员函数
静态成员函数的封装方法与一般函数相同。
// (1) 静态成员函数 (以下两种均可)
Fcn func21 = Object::add_static; // 封装
Fcn func22 = &Object::add_static; // 封装
func21(3, 4); // 调用
func22(3, 4);
2.2.2.非静态成员函数
非静态成员函数比较特殊,因为含有this
指针,需要通过bind()
来实现封装。
// (2) 非静态成员函数
/*
error: conversion from 'void (Object::*)(int, int)' to non-scalar type 'std::function<void(int, int)>' requested function<void(int, int)> fcn3 = Object::add;
*/
Fcn func3 = Object::add; // 编译出错
Object obj;
// &obj的位置就是类的this指针
Fcn func31 = std::bind(Object::add, &obj, placeholders::_1, placeholders::_2); // 编译成功
Fcn func32 = std::bind(&Object::add, &obj, placeholders::_1, placeholders::_2); // 编译成功
func31(1, 2);
func32(1, 2);
2.3.封装仿函数
struct Functor {
void operator() (int a, int b) {
cout << "a + b = " << a + b;
return;
}
}
Functor fcn;
Fcn fcn4 = fcn;
fcn4(1, 2);
2.4.封装匿名函数
auto fcn = [](int a, int b) {
cout << "a + b = " << a + b;
return;
};
Fcn fcn5 = fcn; // 可以发现, lambda函数对象fcn可以被转换为Fcn类型
fcn5(1, 2);
3.应用实例
这是我最近遇到的一个场景,介绍如下:
若一个类A中有一个成员变量是一个函数对象,它调用的是另一个类B中的非静态成员函数。这应该如何实现对类A中的函数对象的赋值操作?
#include <functional>
typedef std::function<void()> callBackFcn;
class A {
private:
callBackFcn cb;
public:
A();
~A();
void doCallBack() {
return cb();
}
}
class B {
public:
void doTask() {
cout << "this is B." << endl;
}
}
这里在A的构造函数中实现对函数对象cb
的赋值操作。
3.1.做法一
// 定义A的构造函数
A::A(callBackFcn fcn) { // 区别: 值传递
cb = fcn;
}
// 实例化A
B b;
A a(std::bind(B::doTask, &b));
a.doCallBack();
3.2.做法二
与做法二的唯一区别在于,这里是通过引用传递来进行函数对象的传参的。但是,重点来了!这里的引用传递必须是常量引用传递,也就是说,必须加上关键字const
。否则,无法通过编译。
若不加const
关键字,就会得到如下的报错。翻译一下就是:不能将非常量的左值引用(这里是指fcn
)绑定到一个右值类型(这里是指std::bind(B::doTask, &b)
)上。加上关键字const
,将函数的形参类型变成常量的左值引用,就可以绑定右值类型了。
error: cannot bind non-const lvalue reference of type 'callBackFcn&' {aka 'std::function<void()>&'} to an rvalue of type 'callBackFcn' {aka 'std::function<void()>'}
// 定义A的构造函数
A::A(const callBackFcn& fcn) { // 区别: 引用传递
cb = fcn;
}
// 实例化A
B b;
A a(std::bind(B::doTask, &b));
a.doCallBack();