C++工程师需要掌握的10个C++11特性
作者:网络转载 发布时间:[ 2014/11/26 11:37:42 ] 推荐标签:C++ 工程师 软件开发
集合循环
C++11扩展了for表达式以支持遍历集合时类似“foreach”样式的功能。新的形式可以遍历C风格的数组、初始化列表以及任何重载了非成员函数begin()和end()的数据集合。
This for each for is useful when you just want to get and do something with the elements of a collection/array and don't care about indexes, iterators or number of elements.
如果你仅仅是想对集合中的元素做点什么,而并不关心集合中的索引、迭代器、元素个数等,那么逐个访问的for语句是个不错的选择。
1 std::map<std::string, std::vector<int> > map;
2 std::vector<int> v;
3 v.push_back(1);
4 v.push_back(2);
5 v.push_back(3);
6 map["one"] = v;
7
8 for(const auto& kvp : map)
9 {
10 std::cout << kvp.first << std::endl;
11
12 for(auto v : kvp.second)
13 {
14 std::cout << v << std::endl;
15 }
16
17 }
18
19 int arr[] = {1,2,3,4,5};
20 for(int& e : arr)
21 {
22 e = e*e;
23 }
override和final标识
我一向认为C++中虚拟方法的设计很差,它没有(现在也没有)一个强制机制来标记虚拟方法在派生类中的重写情况。virtual关键字是可选的,有时你必须沿着继承链找到顶端的父类来判断这个方法是否为虚拟方法,降低了代码可读性。因此我提倡在派生类中使用virtual关键字以让代码更容易阅读。但还是可能出现一些微妙的错误,看看下面的例子:
1 class B
2 {
3 public:
4 virtual void f(short) {std::cout << "B::f" << std::endl;}
5 };
6
7 class D : public B
8 {
9 public:
10 virtual void f(int){std::cout << "D::f" << std::endl;}
11 };
D::f原本要重写B::f的,但由于参数的类型一个是short而另一个是int,造成函数签名不同,因此B::f只是一个拥有相同名字(而且被重载了)的另一个方法,并没有被重写。你可能希望通过指向B的指针访问f()函数输出D::f,但它输出的却是B::f。
Here is another subtle error: the parameters are the same, but the method in the base class is marked const, while method in the derived is not.
还有一个微妙的错误:函数参数相同,但基类中该方法被标记为const,而派生类中没有标记。
1 class B
2 {
3 public:
4 virtual void f(int) const {std::cout << "B::f" << std::endl;}
5 };
6
7 class D : public B
8 {
9 public:
10 virtual void f(int) {std::cout << "D::f" << std::endl;}
11 };
这两个方法仍然是重载的关系而不是重写。如果你通过指向B的指针访问f()输出的是B::f而不是D::f。
现在这个问题得到了解决。C++11新增了两个标记(注意不是关键字):override和final。override表明一个方法是对基类中虚拟方法的重写,final表明派生类中的方法不能重写虚拟方法。第一个例子如下:
1 class B
2 {
3 public:
4 virtual void f(short) {std::cout << "B::f" << std::endl;}
5 };
6
7 class D : public B
8 {
9 public:
10 virtual void f(int) override {std::cout << "D::f" << std::endl;}
11 };
例子中的写法会触发一个编译错误(上文使用const的例子中如果使用override说明符的话也会触发这个错误):
“ 'D::f': 带有重写说明符'override'的方法没有重写任何基类方法 ”
而如果需要把一个方法标记为不能再被重写(这个方法在所在类的派生类中都不能被重写),使用final说明符。可以在基类中使用final,也可以在任何派生类中使用final。派生类中即可以使用override说明符,也可以使用final说明符。
1 class B
2 {
3 public:
4 virtual void f(int) {std::cout << "B::f" << std::endl;}
5 };
6
7 class D : public B
8 {
9 public:
10 virtual void f(int) override final {std::cout << "D::f" << std::endl;}
11 };
12
13 class F : public D
14 {
15 public:
16 virtual void f(int) override {std::cout << "F::f" << std::endl;}
17 };
例子中声明为'final'的方法不能被F::f重写。
强类型的枚举类型
C++中传统的枚举类型有一些缺点:会把枚举数导出到外围域中(当两个不同的枚举类型同时拥有定义了相同名字的枚举数的域时,会导致命名冲突);会隐式地转换为整型,并不能使用用户指定的基础类型。
这些问题在C++11中得到了解决。C++11引进了新的一种枚举类型,叫强类型枚举类型,通过关键字“enum class”指定。它们不再将枚举数导出到外围域中,也不会隐式转换为整型,可以使用用户自定义的基础类型(这种特性也加入了传统枚举类型中)。
1 enum class Options {None, One, All};
2 Options o = Options::All;
智能指针
已有很多文章讨论了这个主题,因此这里仅说一下智能指针的引用计数和自动释放自身的可用内存:
unique_ptr:该指针所指内存资源的所有权不能被共享(没有复制构造函数),但该指针可以转换成另一个unique_ptr指针(存在移动构造函数)。
shared_ptr:该指针所指内存资源的所有权可被共享(如其名)。
weak_ptr:该指针是对由shared_ptr管理的一个对象的引用,但并不会增加引用计数;该指针用来打破循环依赖(设想一下一棵树的情景,父结点持有对子结点的引用(shared_ptr类型),同时子结点也持有对父结点的引用;如果子对父的引用也是shared_ptr类型的话,导致了循环引用,父与子结点都不能被释放)。
auto_ptr类型已经过时不再使用了。如果你需要使用unique_ptr,或者需要使用有所有权需求的shared_ptr指针时,推荐你读一下这篇文章。
下面的例子演示了unique_ptr的用法。如果你想把一个对象的所有权转交给另一个unique_ptr指针,可以用std::move函数(在下一段讨论这个函数)。所有权转交后,放弃所有权的智能指针变为空指针,通过get()方法返回的值为nullptr。
1 void foo(int* p)
2 {
3 std::cout << *p << std::endl;
4 }
5 std::unique_ptr<int> p1(new int(42));
6 std::unique_ptr<int> p2 = std::move(p1); // transfer ownership
7
8 if(p1)
9 foo(p1.get());
10
11 (*p2)++;
12
13 if(p2)
14 foo(p2.get());
下面的例子演示了shared_ptr的用法。和unique_ptr的用法类似,不同之处在于语义:shared_ptr的所有权是共享的。
1 void foo(int* p)
2 {
3 }
4 void bar(std::shared_ptr<int> p)
5 {
6 ++(*p);
7 }
8 std::shared_ptr<int> p1(new int(42));
9 std::shared_ptr<int> p2 = p1;
10
11 bar(p1);
12 foo(p2.get());
第一条声明(指p1的声明)等价于下面这条:
auto p3 = std::make_shared<int>(42);
非成员函数make_shared<T>可以方便地通过单独一次内存分配过程为共享对象和智能指针分配内存,而通过shared_ptr的构造函数进行隐式构造需要至少两次分配过程。多次分配需要额外的开销,有些情况下会造成内存泄漏,比如下面的例子,如果seed()抛出了一个错误那么将引起内存泄漏。
1 void foo(std::shared_ptr<int> p, int init)
2 {
3 *p = init;
4 }
5 foo(std::shared_ptr<int>(new int(42)), seed());
如果使用make_shared则不存在这样的问题。下面的是使用weak_ptr的例子。注意,如果需要访问weak_ptr引用的对象,则必须调用weak_ptr的lock()方法并将返回值传给shared_ptr类型的指针。
1 auto p = std::make_shared<int>(42);
2 std::weak_ptr<int> wp = p;
3
4 {
5 auto sp = wp.lock();
6 std::cout << *sp << std::endl;
7 }
8
9 p.reset();
10
11 if(wp.expired())
12 std::cout << "expired" << std::endl;
如果尝试锁定一个过期的weak_ptr(弱引用的对象已经被释放了),将返回一个空的shared_ptr。
Lambdas表达式
C++11中引进了用于定义匿名函数的lambda表达式,并很快成为了非常重要的特性。它是从函数式编程中借鉴来的强大特性,反过来它带来了其它的特性(或者说引进了其它的函数库)。任何可以用函数对象、函子或std::function的地方都可以使用lambda表达式。如果想详细了解它的语法请戳这里。
1 std::vector<int> v;
2 v.push_back(1);
3 v.push_back(2);
4 v.push_back(3);
5
6 std::for_each(std::begin(v), std::end(v), [](int n) {std::cout << n << std::endl;});
7
8 auto is_odd = [](int n) {return n%2==1;};
9 auto pos = std::find_if(std::begin(v), std::end(v), is_odd);
10 if(pos != std::end(v))
11 std::cout << *pos << std::endl;
需要注意的是递归lambda表达式。比如用lambda表达式来表示Fibonacci方程,如果尝试像下面这样用auto来实现,将导致编译错误。
auto fib = [&fib](int n) {return n < 2 ? 1 : fib(n-1) + fib(n-2);};
error C3533: 'auto &': a parameter cannot have a type that contains 'auto'
error C3531: 'fib': a symbol whose type contains 'auto' must have an initializer
error C3536: 'fib': cannot be used before it is initialized
error C2064: term does not evaluate to a function taking 1 arguments
问题在于auto类型的对象需要通过它的初始语句来推断出具体的类型,而初始语句包含了对它的引用,又需要知道它的具体类型。这造成了循环依赖问题。解决的关键在于打断依赖循环,可通过std::function来明确地指明函数的具体类型。
std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);};
相关推荐
更新发布
功能测试和接口测试的区别
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