C++11学习笔记

1、类型推导

auto

在C++11之前,auto关键字用来指定存储期。在新标准中,它的功能变为类型推断。auto现在成了一个类型的
占位符,通知编译器去根据初始化代码推断所声明变量的真实类型。各种作用域内声明变量都可以用到它。
auto i = 42; //auto is an int
auto l = 42LL;//auto is an long long
auto p = new foo();//p is a foo*
 
使用auto通常意味着更短的代码(除非你使用类型是int,它会比auto少一个字母)。试想一下当你遍历STL容器时
需要声明的那些迭代器(iterator)。现在不需要去声明那些typedef就可以得到简洁的代码了。
std::map<std::string,std::vector<int>> map;
for(auto it = begin(map);it != end(map);++it)
{
 
}
需要注意的是,auto不能用来声明函数的返回值,但如果函数有一个尾随的返回类型时,auto是可以出现在函数声明
中返回值位置。这种情况下,auto并不是告诉编译器去推断返回类型,而是指引编译器去函数的末端寻找返回值类型。
template<typename T1,typename T2>
auto compose(T1 t1,T2 t2)->decltype(t1+t2)
{
 return t1 + t2;
}
 
auto v = compose(2,3.14); 
2、nullptr
以前都是用0来表示空指针的,但由于0可以被隐式转换为整型,还会存在一些问题。关键字nullptr是std::nullptr_t类型的值,用来指代空指针
。nullptr和任何指针类型以及类成员指针类型的空值之间可以发生隐式类型转换,同样可以隐式转换为bool型(取值为false)。但是不存在到
整型的隐式转换。
void foo(int *p){}
void bar(std::shared_ptr<int> p){}
int* p1 = NULL;
int* p2 = nullptr;
if(p1 == p2)
{
}
 
foo(nullptr);
bar(nullptr);
 
bool f = nullptr;
int i = nullptr;
//error: A native nullptr can only be converted to bool or,using reinterpret_cast,to an integfral type
为了向前兼容,0仍然是个合法的空指针值
 
3、以范围为基础的for循环
Boost C++定义了许多"范围(range)"的概念,范围表现有如受控制的串行(list),持有容器中的两点。有序容器是范围概念的超集(superset),有序容器中的两个迭代器(iterator)也能定义一个范围。这些概念以及操作的算法,将被并入C++11标准程序库。不过C++11将会以语言层次的
支持来提供范围概念的效用。
for语句将允许简单的范围迭代:
int my_array[5] = {1,2,3,4,5};
for(int &x : my_array)
{
 x *= 2;
}
上面for语句的第一部分定义被用来做范围迭代的变量,就像被声明在一般for循环的变量一样,其作用域仅只于循环的范围。而在":"之后的第二区块,代表将被迭代的范围。这样一来,就有了能够允许C-Style数组被转换成范围概念的概念图。这也可以是std::vector,或是其他范围概念的对象。
 
4、override和final
C++中虚函数的设计很差劲,因为时至今日仍然没有一个强制的机制来标识虚函数会在派生类里被改写。virtual关键字是可选
的,这使得阅读代码变得很费劲。因为可能需要追溯到继承体系的源头才能确定某个方法是否是虚函数。为了增加可读性,我
总是在派生类里写上virtual关键字,并且也鼓励大家都这么做,即使这样,仍然会产生一些微妙的错误。
 
class B
{
public:
 virtual void f(short){std::cout<<"B::f"<<std::endl;}
};
 
class D : public B
{
public:
 virtual void f(int){std::cout<<"D::F"<<endl;}
};
 
D::f按理应当重写B::f。然而二者的声明是不同的,一个参数是short,另一个是int。因此D::f只是拥有同样名字的
另一个函数(重载)而不是重写。当你期望通过B类型的指针调用f()打印D::f,但实际会打出B::f.
 
另一个很微妙的错误情况:参数相同,但是基类的函数是const的,派生类的函数却不是。
class B
{
public:
 virtual void f(int) const {std::cout<<"B::f"<<std::endl;}
};
 
class D : public B
{
public:
 virtual void f(int){std::cout<<"D::F"<<endl;}
};
同样,这两个函数是重载二不是重写,所以你通过B类型指针调用f()将打印B::f,而不是D::f。
现在,有一种方式能描述你的意图。新标准加入了两个新的标识符(不是关键字)::
1、override,表示函数应当重写基类中的虚函数。
2、final,表示派生类不应当重写这个虚函数。
 
第一个例子如下:
class B
{
public:
 virtual void f(short){std::cout<<"B::f"<<std::endl;}
};
 
class D : public B
{
public:
 virtual void f(int) override {std::cout<<"D::F"<<endl;}
};
现在这将触发一个编译错误
"D::f":method with override specifier 'override' did not override any base class methods
 
另一方面,如果你希望函数不要再被派生类进一步重写,你可以把它标识为final。可以在基类或任何派生类中
使用final。在派生类中,可以同时使用override和final标识。
class B
{
public:
 virtual void f(int) const {std::cout<<"B::f"<<std::endl;}
};
 
class D : public B
{
public:
 virtual void f(int) override final {std::cout<<"D::F"<<endl;}
};
 
class F : public D
{
public:
 virtual void f(int) override{std::cout<<"F::f"<<std::endl;}
};
被标记成final的函数将不能再被F::f重写。
 
5、strongly-typed enums强类型枚举
传统的C++枚举类型存在一些缺陷:它们会将枚举常量暴露在外层作用域中(这可能导致名字冲突,如果同
一个作用域中存在两个不同的枚举类型,但是具有相同的枚举常量就会冲突),而且它们会被隐式转换为整型,
无法拥有特定的用户定义类型。
 
在C++11中通过引入了一个称为强类型枚举的新类型,修正了这种情况。强类型枚举有关键字enum class标识。
它不会将枚举常量暴露到外层作用域中,也不会隐式转换为整数,并且拥有用户指定的特定类型(传统枚举也增加
了这个性质)。
enum class Options{None,One,All};
Options o = Options::All;
 
6、Smart Pointers智能指针
已经有成千上万的文章讨论这个问题,所以我只想说:现在能使用的,带引用计数,并且能自动释放内存的智能
指针包括一下几种:
a、unique_ptr:如果内存资源的所有权不需要共享,就应当使用这个(它没有拷贝构造函数),但是它可以转让
给另一个unique_ptr(存在move构造函数)。
b、share_ptr:如果内存资源需要共享,那么使用这个。
c、weak_ptr:持有被shared_ptr所管理对象的引用,但是不会改变引用计数值,它被用来打破依赖循环(想象在
一个tree结构中,父节点通过一个共享所有权的引用(shared_ptr)引用子节点,同时子节点又必须持有父节点的
引用。如果这第二个引用也共享所有权,就会导致一个循环,最终导致两个节点内存都无法释放)
 
另一方面,auto_ptr已经被废弃,不会再使用了。
什么时候使用unique_ptr,什么时候使用shared_ptr取决于所有权的需求,我建议阅读以下讨论:

http://stackoverflow.com/questions/15648844/using-smart-pointers-for-class-members

一下第一个例子使用了unique_ptr。如果你想把对象所有权转移给另一个unique_ptr,需要使用std::move。在所有权
转移后,交出所有权的智能指针为空,get()函数将返回nullptr。
void foo(int *p)
{
 std::cout<<*p<<std::endl;
}
 
std::unique_ptr<int> p1(new int(42));
std::unique_ptr<int> p2 = std::move(p1);
//transfer ownership
 
if(p1)
foo(p1.get());
 
(*p2)++;
if(p2)
foo(p2.get());
 
第二处例子展示了shared_ptr。用法相似,但语义不同,此时所有权是共享的。
void foo(int* p)
{
 std::cout<<*p<<std::endl;
}
 
void bar(std::shared_ptr<int> p)
{
 ++(*p);
}
std::shared_ptr<int> p1(new int(42));
std::shared_ptr<int> p2 = p1;
 
bar(p1);
foo(p2.get());
第一个声明和以下这行是等价的:
auto p3 = std::make_shared<int>(42);
make_shared<T>是一个非成员函数,使用它的好处是可以一次性分配共享对象和智能指针自身的内存。而显示地使用
shared_ptr构造函数来构造则至少需要两次内存分配。除了会产生额外的开销,还可能会导致内存泄漏。在下面这个例子
中,如果seed()抛出一个错误就会产生内存泄漏。
 
void foo(std::shared_ptr<int> p,int init)
{
 *p = init;
}
 
foo(std::shared_ptr<int>(new int(42)),seed());
如果使用make_shared就不会有这个问题了。第三个例子展示了weak_ptr。注意,你必须调用lock()来获得被引用对象的
shared_ptr,通过它才能访问这个对象。
auto p = std::make_shared<int>(42);
std::weak_ptr<int> wp = p;
{
 auto sp = wp.lock();
 std::cout<<*sp<<std::endl;
}
p.reset();
if(wp.expired())
std::cout<<"expired"<<std::endl;
如果试图锁定(lock)一个过期(指被弱引用对象已经被释放)的weak_ptr,那你将获得一个空 的shared_ptr.
 
7、Lambdas

在标准C++,特别是当使用C++标准程序库算法函数诸如sort和find,用户经常希望能够在算法函数调用的附近定义

一个临时的述部函数(又称谓词函数,predicate function)。由于语言本身允许在函数内部定义类型,可以考虑使用

函数对象,然而这通常既麻烦又冗赘,也阻碍了代码的流程,此外标准C++不允许定义于函数内部的类型被用于模板,所

以前述的作法是不可行的。

 

C++11对lambda的支持可以解决上述问题

一个lambda函数可以用如下的方式定义:

[](int x,int y){return x+y;}

这个不具名函数返回类型是decltype(x+y)。只有在lambda函数符合"return expression"的形式下,它的回返类型

才能被忽略。在前述的情况下,lambda函数仅能为一个述句。

 

在一个更为复杂的例子中,回返类型可以被明确的指定如下:

[](int x,int y)->int{int z = x+y; return z+x;}

本例中,一个暂时的变量z被创建用来存储中间结果。如同一般的函数,z的值不会保留到一下次该不具名函数再次被

调用时。

 

如果lambda函数没有返回值(例如void),其回返类型可以被完全忽略。定义在与lambda函数相同作用域的变量参考

也可以被使用。这种的变量集合一般称作closure(闭包)。

[] //没有定义任何参数。使用未定义参数会导致错误

[x,&y] //x以传值方式传入(预设),y以传引用方式传入。

[&]    //任何被使用到的外部参数皆隐式地以引用方式加以使用。

[=]    //任何被使用到的外部参数皆隐式地以传值方式加以使用

[&,x]    //x显示地以传值方式加以使用,其余参数以引用方式加以使用

[=,&z] //z显示地以传引用的方式加以使用。其余参数以传参数方式加以使用。

posted @ 2016-06-05 17:17  Cogvr  阅读(127)  评论(0)    收藏  举报