一、C++11使用using定义别名(替代typedef)
- 模块转自:C++11使用using定义别名(替代typedef)
- typedef 仅定义别名,常用定义结构体、函数指针。
- using 可完全取代(还可用来泛型编程),主要用于定义函数指针、匿名函数、C++容器。
大家都知道,在 C++ 中可以通过 typedef 重定义一个类型:typedef unsigned int uint_t;
被重定义的类型并不是一个新的类型,仅仅只是原有的类型取了一个新的名字。因此,下面这样将不是合法的函数重载:
void func(unsigned int);
void func(uint_t); // error: redefinition
使用 typedef 重定义类型是很方便的,但它也有一些限制,比如,无法重定义一个模板。
想象下面这个场景:
typedef std::map<std::string, int> map_int_t;
// ...
typedef std::map<std::string, std::string> map_str_t;
// ...
我们需要的其实是一个固定以 std::string 为 key 的 map,它可以映射到 int 或另一个 std::string。然而这个简单的需求仅通过 typedef 却很难办到。
因此,在 C++98/03 中往往不得不这样写:
template <typename Val>
struct str_map
{
typedef std::map<std::string, Val> type;
};
// ...
str_map<int>::type map1;
// ...
一个虽然简单但却略显烦琐的 str_map 外敷类是必要的。这明显让我们在复用某些泛型代码时非常难受。
现在,在 C++11 中终于出现了可以重定义一个模板的语法。请看下面的示例:
template <typename Val>
using str_map_t = std::map<std::string, Val>;
// ...
str_map_t<int> map1;
这里使用新的 using 别名语法定义了 std::map 的模板别名 str_map_t。比起前面使用外敷模板加 typedef 构建的 str_map,它完全就像是一个新的 map 类模板,因此,简洁了很多。
实际上,using 的别名语法覆盖了 typedef 的全部功能。先来看看对普通类型的重定义示例,将这两种语法对比一下:
// 重定义unsigned int
typedef unsigned int uint_t;
using uint_t = unsigned int;
// 重定义std::map
typedef std::map<std::string, int> map_int_t;
using map_int_t = std::map<std::string, int>;
可以看到,在重定义普通类型上,两种使用方法的效果是等价的,唯一不同的是定义语法。
typedef 的定义方法和变量的声明类似:像声明一个变量一样,声明一个重定义类型,之后在声明之前加上 typedef 即可。这种写法凸显了 C/C++ 中的语法一致性,但有时却会增加代码的阅读难度。比如重定义一个函数指针时:
typedef void (*func_t)(int, int);
与之相比,using 后面总是立即跟随新标识符(Identifier),之后使用类似赋值的语法,把现有的类型(type-id)赋给新类型:
using func_t = void (*)(int, int);
从上面的对比中可以发现,C++11 的 using 别名语法比 typedef 更加清晰。因为 typedef 的别名语法本质上类似一种解方程的思路。而 using 语法通过赋值来定义别名,和我们平时的思考方式一致。
1.2 using 和 std::function
using handle_connected = std::function<void(std::shared_ptr<Session>)>
using handle_close = std::function<void(std::shared_ptr<Session>)>
using handle_check_recevie = std::function<bool(std::shared_ptr<Session>, const char * pData, const int &inLength, int &needReadLength)>
using handle_deal_data = std::function<bool(std::shared_ptr<Session>, const std::string &dataIn, std::string &dataOut)>
二、函数对象
2.1 std::function
类模板 std::function 是通用多态函数包装器。 std::function 的实例能存储、复制及调用任何可复制构造 (CopyConstructible) 的可调用 (Callable) 目标——函数(通过其指针)、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标,则称它为空。调用空 std::function 的目标导致抛出 std::bad_function_call 异常。
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
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();
// 存储到成员函数的调用
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
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) << "; "; }
}
注:
1.上面都是直接赋值使用,还可作为参数来传递。
2.在实际使用中都用 auto 关键字来代替std::function… 这一长串了。
2.2 std::bind
函数模板 std::bind 生成 f 的转发调用包装器。调用此包装器等价于以一些绑定到 args 的参数调用 f。
参数
- f - 可调用 (Callable) 对象(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针)
- args - 要绑定的参数列表,未绑定参数会被命名空间 std::placeholders 的占位符 _1, _2, _3… 替换
返回值
某个未指定类型 T 的函数对象 g,其中 std::is_bind_expression::value 是 true。它有下列属性:
……
实例
#include <functional>
#include <iostream>
#include <memory>
#include <random>
void f(int n1, int n2, int n3, const int& n4, int n5)
{
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
int g(int n1)
{
return n1;
}
struct Foo
{
void print_sum(int n1, int n2)
{
std::cout << n1 + n2 << '\n';
}
int data = 10;
};
int main()
{
using namespace std::placeholders; // 对于 _1, _2, _3...
std::cout << "1) 参数重排序和按引用传递:";
int n = 7;
// ( _1 与 _2 来自 std::placeholders ,并表示将来会传递给 f1 的参数)
auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
n = 10;
f1(1, 2, 1001); // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001
// 进行到 f(2, 42, 1, n, 7) 的调用
std::cout << "2) 使用 lambda 达成相同效果:";
n = 7;
auto lambda = [&ncref = n, n](auto a, auto b, auto /* 未使用 */)
{
f(b, 42, a, ncref, n);
};
n = 10;
lambda(1, 2, 1001); // 等同于调用 f1(1, 2, 1001)
std::cout << "3) 嵌套 bind 子表达式共享占位符:";
auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
f2(10, 11, 12); // 进行到 f(12, g(12), 12, 4, 5); 的调用
std::cout << "4) 以分布绑定随机数生成器:";
std::default_random_engine e;
std::uniform_int_distribution<> d(0, 10);
std::function<int()> rnd = std::bind(d, e); // e 的一个副本存储于 rnd
for (int n = 0; n < 10; ++n)
std::cout << rnd() << ' ';
std::cout << '\n';
std::cout << "5) 绑定指向成员函数指针:";
Foo foo;
auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
f3(5);
std::cout << "6) 绑定是指向成员函数指针的 mem_fn:";
auto ptr_to_print_sum = std::mem_fn(&Foo::print_sum);
auto f4 = std::bind(ptr_to_print_sum, &foo, 95, _1);
f4(5);
std::cout << "7) 绑定指向数据成员指针:";
auto f5 = std::bind(&Foo::data, _1);
std::cout << f5(foo) << '\n';
std::cout << "8) 绑定是指向数据成员指针的 mem_fn:";
auto ptr_to_data = std::mem_fn(&Foo::data);
auto f6 = std::bind(ptr_to_data, _1);
std::cout << f6(foo) << '\n';
std::cout << "9) 使用智能指针调用被引用对象的成员:";
std::cout << f6(std::make_shared<Foo>(foo)) << '\n'
<< f6(std::make_unique<Foo>(foo)) << '\n';
}