C++工程师需要掌握的10个C++11特性
作者:网络转载 发布时间:[ 2014/11/26 11:37:42 ] 推荐标签:C++ 工程师 软件开发
非成员的begin()和end()函数
你或许已经注意到了上面例子中频繁出现的非成员函数begin()和end()。这是新加进标准库的函数,提升了标准库的统一性和一致性,并让工程师可以编写更通用的程序。这两个函数适用于所有的STL容器,而且还是可重载的,可以扩展应用到任何的类型,同时提供了应用于C风格数组的重载。
让我们再看一下前面输出vector数组元素并查找第一个奇数的例子。如果将std::vector数组换成C风格数组,代码可能看起来像这样:
1 int arr[] = {1,2,3};
2 std::for_each(&arr[0], &arr[0]+sizeof(arr)/sizeof(arr[0]), [](int n) {std::cout << n << std::endl;});
3
4 auto is_odd = [](int n) {return n%2==1;};
5 auto begin = &arr[0];
6 auto end = &arr[0]+sizeof(arr)/sizeof(arr[0]);
7 auto pos = std::find_if(begin, end, is_odd);
8 if(pos != end)
9 std::cout << *pos << std::endl;
使用了非成员函数begin()和end()之后它是这个样子的:
1 int arr[] = {1,2,3};
2 std::for_each(std::begin(arr), std::end(arr), [](int n) {std::cout << n << std::endl;});
3
4 auto is_odd = [](int n) {return n%2==1;};
5 auto pos = std::find_if(std::begin(arr), std::end(arr), is_odd);
6 if(pos != std::end(arr))
7 std::cout << *pos << std::endl;
这段代码和std::vector版本的基本相同,也是说我们可以为所有支持begin()和end()方法的类型写一个通用的方法。
1 template <typename Iterator>
2 void bar(Iterator begin, Iterator end)
3 {
4 std::for_each(begin, end, [](int n) {std::cout << n << std::endl;});
5
6 auto is_odd = [](int n) {return n%2==1;};
7 auto pos = std::find_if(begin, end, is_odd);
8 if(pos != end)
9 std::cout << *pos << std::endl;
10 }
11
12 template <typename C>
13 void foo(C c)
14 {
15 bar(std::begin(c), std::end(c));
16 }
17
18 template <typename T, size_t N>
19 void foo(T(&arr)[N])
20 {
21 bar(std::begin(arr), std::end(arr));
22 }
23
24 int arr[] = {1,2,3};
25 foo(arr);
26
27 std::vector<int> v;
28 v.push_back(1);
29 v.push_back(2);
30 v.push_back(3);
31 foo(v);
静态断言和类型特性
静态断言在编译时进行一次断言检查。如果断言为真,什么也不做;如果断言为假,编译器会显示特定的错误消息。
1 template <typename T, size_t Size>
2 class Vector
3 {
4 static_assert(Size < 3, "Size is too small");
5 T _points[Size];
6 };
7
8 int main()
9 {
10 Vector<int, 16> a1;
11 vector<double, 2> a2;
12 return 0;
13 }
error C2338: Size is too small
see reference to class template instantiation 'Vector<T,Size>' being compiled
with
[
T=double,
Size=2
]
静态断言配合类型特性一起使用将更加强大。所谓类型特性是在编译阶段提供类型信息的一系列类,相关定义位于头文件<type_traits>中。这个头文件中定义了好几种类:辅助类,用于创建编译时的常量;类型特性类,用于获取编译时关于类型的信息;类型转换类,对已存在的类型进行转换以生成新的类型。
在下面的例子中,add函数原本被设定为只能处理整型。
1 template <typename T1, typename T2>
2 auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
3 {
4 return t1 + t2;
5 }
然而如果下面这样写的话也不会报编译错误:
1 std::cout << add(1, 3.14) << std::endl;
2 std::cout << add("one", 2) << std::endl;
事实上程序输出的结果为4.14和"e"。但如果增加了编译时断言,上面的两行代码都会导致编译时错误。
1 template <typename T1, typename T2>
2 auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
3 {
4 static_assert(std::is_integral<T1>::value, "Type T1 must be integral");
5 static_assert(std::is_integral<T2>::value, "Type T2 must be integral");
6
7 return t1 + t2;
8 }
error C2338: Type T2 must be integral
see reference to function template instantiation 'T2 add<int,double>(T1,T2)' being compiled
with
[
T2=double,
T1=int
]
error C2338: Type T1 must be integral
see reference to function template instantiation 'T1 add<const char*,int>(T1,T2)' being compiled
with
[
T1=const char *,
T2=int
]
移动语义
移动语义是另一个C++11中重要而且广泛讨论的话题,据此可以写好几篇文章而不仅仅是一个段落。因此我不会过多地讨论细节,但如果你还不熟悉这个话题,建议找几篇相关的文章来读一读。
C++11通过引进右值引用(通过&&指定)的概念来区别一个引用是左值的还是右值的。左值指有具体名字的对象,右值指没有具体名字的对象(临时对象)。移动语义则允许修改右值(之前认为右值是不变的,和const T&类型没有区别)。
C++的类或结构体通常有一些隐式成员函数:默认构造函数(只有当没有其它显示定义的构造函数时才存在),复制构造函数,析构函数,以及复制赋值运算符。复制构造函数和复制赋值运算符进行按位复制(也叫浅复制),即按位复制变量。当定义的类包含一些指向对象的指针时,它们仅仅复制了指针的值而不复制指针所指的对象。在一些情况下这样做OK,但更多时候我们真正想做的是进行深复制,即需要复制指针所指的对象而不是指针的值。这种情况下需要显式地写出复制构造函数以及复制赋值运算符以进行深复制。
如果你需要进行初始化或复制的对象是一个右值(临时的对象)则应该怎么办?你依然需要复制它的值,但很快右值对象会消失。这意味着包括内存分配和内存拷贝在内的所有操作的开销都是没有必要的。
现在来看看移动构造函数和移动赋值操作符。这两个特殊的函数都有一个T&&类型的参数,这个参数是一个右值。记住,它们都可以修改对象,比如“盗取”它们的指针所指向的对象等。例如,一个容器的实现(vector或者queue等)可能包含一个指向元素数组的指针。当从一个临时量中实例化一个对象时,我们不需要:分配另一个数组,从临时量中拷贝值出来,然后在删除它时释放临时量所占的内存。我们只需要复制指向已分配数组的指针的值即可,这样节省了分配内存、复制数组元素和随后的释放内存的开销。
下面的例子演示了虚拟缓存的实现。缓存通过名字进行识别,有一个指向元素类型为T的数组的指针(封装在一个std::unique_ptr中),还有一个存储数组长度的变量。
1 template <typename T>
2 class Buffer
3 {
4 std::string _name;
5 size_t _size;
6 std::unique_ptr<T[]> _buffer;
7
8 public:
9 // default constructor
10 Buffer():
11 _size(16),
12 _buffer(new T[16])
13 {}
14
15 // constructor
16 Buffer(const std::string& name, size_t size):
17 _name(name),
18 _size(size),
19 _buffer(new T[size])
20 {}
21
22 // copy constructor
23 Buffer(const Buffer& copy):
24 _name(copy._name),
25 _size(copy._size),
26 _buffer(new T[copy._size])
27 {
28 T* source = copy._buffer.get();
29 T* dest = _buffer.get();
30 std::copy(source, source + copy._size, dest);
31 }
32
33 // copy assignment operator
34 Buffer& operator=(const Buffer& copy)
35 {
36 if(this != ©)
37 {
38 _name = copy._name;
39
40 if(_size != copy)
41 {
42 _buffer = nullptr;
43 _size = copy._size;
44 _buffer = _size > 0 ? new T[_size] : nullptr;
45 }
46
47 T* source = copy._buffer.get();
48 T* dest = _buffer.get();
49 std::copy(source, source + copy._size, dest);
50 }
51
52 return *this;
53 }
54
55 // move constructor
56 Buffer(Buffer&& temp):
57 _name(std::move(temp._name)),
58 _size(temp._size),
59 _buffer(std::move(temp._buffer))
60 {
61 temp._buffer = nullptr;
62 temp._size = 0;
63 }
64
65 // move assignment operator
66 Buffer& operator=(Buffer&& temp)
67 {
68 assert(this != &temp); // assert if this is not a temporary
69
70 _buffer = nullptr;
71 _size = temp.size;
72 _buffer = std::move(temp._buffer);
73
74 _name = std::move(temp._name);
75
76 temp._buffer = nullptr;
77 temp._size = 0;
78
79 return *this;
80 }
81 };
82
83 template <typename T>
84 Buffer<T> getBuffer(const std::string& name)
85 {
86 Buffer<T> b(name, 128);
87 return b;
88 }
89
90 int main()
91 {
92 Buffer<int> b1;
93 Buffer<int> b2("buf2", 64);
94 Buffer<int> b3 = b2;
95 Buffer<int> b4 = getBuffer<int>("buf4");
96 b1 = getBuffer<int>("buf5");
97 return 0;
98 }
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11