1. 智能指针和nullptr
智能指针参见博文:https://blog.csdn.net/okiwilldoit/article/details/80062220
nullptr
nullptr 出现的目的是为了替代 NULL。
传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。
C++ 不允许直接将 void * 隐式转换到其他类型,但如果 NULL 被定义为 ((void*)0),那么当编译char *ch = NULL;时,NULL 只好被定义为 0。而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,考虑:
void foo(char *);
void foo(int);
对于这两个函数来说,如果 NULL 又被定义为了 0 那么 foo(NULL); 这个语句将会去调用 foo(int),从而导致代码违反直观。
为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。
nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
当需要使用 NULL 时候,养成直接使用 nullptr的习惯。
2. auto指定符
对于变量,指定其类型将从其初始化器自动推导而出。auto的自动类型推导,用于从初始化表达式中推断出变量的数据类型。
通过auto的自动类型推导,可以大大简化我们的编程工作。auto实际上是在编译时对变量进行了类型推导,所以不会对程序的运行效率造成不良影响。
auto在stl遍历中最常用,如:
vector<string> table;
for (const auto& itr : table) {
…
}
3. 构造函数 = default/delete、DISABLE_COPY_AND_ASSIGN
myclass(const myclass &other)= delete;//默认删除拷贝构造函数,无法拷贝构造
myclass() = default;//默认存在,可以构造
DISABLE_COPY_AND_ASSIGN(myclass);
这两个功能都是为了限制构造函数的功能。有时候,进行类体设计时,会发现某个类的对象是独一无二的,没有完全相同的对象,也就是对该类对象做副本没有任何意义。
因此,需要限制编译器自动生动的拷贝构造函数和赋值构造函数。
4. emplace用法
在使用vector,map,set等容器的过程中,我们会大量用到的操作就是插入操作,比如vector的push_back,map的insert,set的insert。
这些插入操作会涉及到两次构造,首先是对象的初始化构造,接着在插入的时候会复制一次,会触发拷贝构造。但是很多时候我们并不需要两次构造带来效率的浪费,如果可以在插入的时候直接构造,就只需要构造一次就够了。
C++11标准已经有这样的语法可以直接使用了,那就是emplace。vector有两个函数可以使用:emplace,emplace_back。emplace类似insert,emplace_back类似push_back。C++11中有大量关于减少内存拷贝的优化,emplace也是一种。
当调用insert时,我们将元素类型的对象传递给insert,元素的对象被拷贝到容器中,而当我们使用emplace时,我们将参数传递元素类型的构造函数,emplace使用这些参数在容器管理的内存空间中直接构造元素。
#include <iostream>
#include <vector>
using namespace std;
class Hello {
public:
Hello(int a) : a_(a) {
cout << "construct:" << a_ << endl;
}
~Hello() {
cout << "deconstruct:" << a_ << endl;
}
private:
int a_;
};
int main(int argc, char const *argv[]) {
vector<Hello> res;
res.push_back(Hello(1));
res.emplace_back(2);
return 0;
}
编译:
g++ -std=c++11 emplace.cpp
运行结果:
construct:1
deconstruct:1
construct:2
deconstruct:1
deconstruct:1
deconstruct:2
可以看出,用push_back会多一次临时构造。
5. atomic关键字
std::atomic_flag
std::atomic_flag是一个原子的布尔类型,可支持两种原子操作:
test_and_set
如果atomic_flag对象被设置,则返回true;
如果atomic_flag对象未被设置,则设置之,返回false。
clear
清除atomic_flag对象。
std::atomic_flag可用于多线程之间的同步操作,类似于linux中的信号量。使用atomic_flag可实现mutex。
std::atomic<T>
std::atomic对int, char, bool等数据结构进行原子性封装,在多线程环境中,对std::atomic对象的访问不会造成竞争-冒险。
利用std::atomic可实现数据结构的无锁设计。如初始化一个atomic值,std::atomic foo(0); atomic支持++和–的原子操作。
6. std::lock_guard和unique_lock
C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为。通常的做法是在修改共享数据成员的时候进行加锁–mutex。
在使用锁的时候通常是在对共享数据进行修改之前进行lock操作,在写完之后再进行unlock操作,进场会出现由于疏忽导致由于lock之后在离开共享成员操作区域时忘记unlock,导致死锁。
针对以上的问题,C++11中引入了std::unique_lock与std::lock_guard两种数据结构。通过对lock和unlock进行一次薄的封装,实现自动unlock的功能。这种思路与智能指针类似。
std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。.
7. std::bind和std::placeholders
bind可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调 用实体,这种机制在回调函数的使用过程中也颇为有用。
**bind的思想实际上是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。**而且这种绑定是非常灵活的,不论是普通函数、函数对象、还是成员函数都可以绑定,而且其参数可以支持占位符,比如你可以这样绑定一个二元函数auto f = bind(&func, _1, _2);,调用的时候通过f(1,2)实现调用。
std::placeholders 命名空间含有占位对象 [_1, . . . _N] ,其中 N 是实现定义的最大数字。它用于std::bind 表达式用作参数时,占位符对象被存储于生成的函数对象,而以未绑定参数调用函数对象时,每个占位符 _N 被对应的第 N 个未绑定参数替换。
示例代码如下:
#include <iostream>
#include <functional>
using namespace std;
int TestFunc(int a, char c, float f) {
cout << a << endl;
cout << c << endl;
cout << f << endl;
return a;
}
int main() {
auto bindFunc1 = bind(TestFunc, std::placeholders::_1, 'A', 100.1);
bindFunc1(10);
cout << "=================================\n";
auto bindFunc2 = bind(TestFunc, std::placeholders::_2, std::placeholders::_1, 100.1); bindFunc2('B', 10);
cout << "=================================\n";
auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
bindFunc3(100.1, 30, 'C');
return 0;
}
8. std::future
类模板 std::future 提供访问异步操作结果的机制:
(通过 std::async 、 std::packaged_task 或 std::promise 创建的)异步操作能提供一个 std::future 对象给该异步操作的创建者。
然后,异步操作的创建者能用各种方法查询、等待或从 std::future 提取值。若异步操作仍未提供值,则这些方法可能阻塞。
异步操作准备好发送结果给创建者时,它能通过修改链接到创建者的 std::future 的共享状态(例如 std::promise::set_value )进行。
std::future可以从异步任务中获取结果,一般与std::async配合使用,std::async用于创建异步任务,实际上就是创建一个线程执行相应任务。
示例代码:
#include <iostream>
#include <future>
#include <thread>
int main()
{
// future from a packaged_task
std::packaged_task<int()> task([]{ return 7; }); // wrap the function
std::future<int> f1 = task.get_future(); // get a future
std::thread t(std::move(task)); // launch on a thread
// future from an async()
std::future<int> f2 = std::async(std::launch::async, []{ return 8; });
// future from a promise
std::promise<int> p;
std::future<int> f3 = p.get_future();
std::thread( [&p]{ p.set_value(9); }).detach();
std::cout << "Waiting..." << std::flush;
f1.wait();
f2.wait();
f3.wait();
std::cout << "Done!\nResults are: "
<< f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
t.join();
}
promise的官方网站上的示例p.set_value_at_thread_exit函数c+11还不支持,所以改成p.set_value。
编译:
g++ -std=c++11 -pthread future.cpp
输出:
Waiting...Done!
Results are: 7 8 9
9. std::move()函数
左值(lvalue)和右值(rvalue)是 c/c++ 中一个比较晦涩基础的概念,不少写了很久c/c++的人甚至没有听过这个名字,但这个概念到了 c++11 后却变得十分重要,它们是理解 move/forward 等新语义的基础。
左值右值的定义
在 C 语言中,通常来说有名字的变量就是左值(int a = 3中的a),而由运算操作(加减乘除,函数调用返回值等)所产生的**中间结果(没有名字)**就是右值,如上的 3 + 4, a + b 等。
概括的讲,凡是能够取地址的可以称之为左值,反之称之为右值,C++中并没有对左值和右值给出明确的定义,从其解决手段来看类似上面的定义,当然我们还可以定义为:有名字的对象为左值,没有名字的对象为右值。
左值既能够出现在等号左边,也能出现在等号右边的变量(或表达式)。而右值只能出现在等号右边。示例:
class A {};
A a; // a为左值,因为其有明确名字,且对a进行 &a 是合法的。
void Test(A a) { __Test(a); }
Test(A()); // A() 为右值,因为A()产生一个临时对象,临时对象没有名字且无法进行 &取址操作。
左值引用和右值引用
9.1 左值引用
左值引用的基本语法:type &引用名 = 左值表达式;
9.2 右值引用
右值引用的基本语法type &&引用名 = 右值表达式;
右值引用在代码优化方面会经常用到。
右值引用的“&&”中间不可以有空格。
move函数的作用将临时变量的数据直接转移到新的变量上,以减少一次拷贝构造,从而减少一次资源的创建和释放。move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。
从它的源代码来看,它实际上是转化为右值引用,即直接用右值的临时数据,不会再去赋值构造新的中间变量:
static_cast<typename std::remove_reference<T>::type&&>(t)
如string类在赋值或者拷贝构造函数中会声明char数组来存放数据,然后把原string中的 char 数组被析构函数释放,如果a是一个临时变量,则上面的拷贝,析构就是多余的,完全可以把临时变量a中的数据直接 “转移” 到新的变量下面即可。
下面举个例子说明:
#include <iostream>
#include <vector>
using namespace std;
class Hello {
public:
Hello(int a) {
a_ = a;
cout << "construct:" << a_ << endl;
}
Hello(const Hello& h) {
a_ = h.a_;
cout << "copy construct:" << a_ << endl;
}
Hello& operator=(const Hello& h) {
a_ = h.a_;
cout << "operator=" << a_ << endl;
return *this;
}
~Hello() {
cout << "deconstruct:" << a_ << endl;
}
private:
int a_;
};
int main(int argc, char const *argv[]) {
vector<Hello> res;
Hello h1(1);
res.push_back(h1);
//调用移动构造函数,掏空h2,掏空后,最好不要使用h2
Hello h2(2);
res.push_back(std::move(h2));
return 0;
}
运行结果:
construct:1
copy construct:1
construct:2
copy construct:2
copy construct:1 //push到vector时的构造拷贝,用move后就会少一次拷贝构造
deconstruct:1
deconstruct:2
deconstruct:1
deconstruct:1
deconstruct:2
从实际效果来看,move可以减少一次构造拷贝。
10.virtual函数加override、constexpr
C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。
C++11允许声明constexpr类型来由编译器检验变量的值是否是一个常量表达式。
声明为constexpr的必须是一个常量,并且只能用常量或者常量表达式来初始化。
11. Lambda表达式
C++11 引入了lambda表达式,lambda表达式在其所在的位置上定义了一个匿名函数对象。
lambda表达式就是一个函数对象。当编写了一个lambda表达式的时候,编译器将该表达式翻译成一个未命名类的未命名对象。
C++中lambda表达式的构成:
一个标准的lambda表达式包括:捕获列表、参数列表、mutable指示符、尾置返回类型(->返回类型)和函数体:
[capture list] (params list) mutable exception-> return type { function body }
各项具体含义如下:
capture list:捕获外部变量列表
params list:形参列表
mutable指示符:用来说用是否可以修改捕获的变量
exception:异常设定
return type:返回类型
function body:函数体
此外,我们还可以省略其中的某些成分来声明“不完整”的lambda表达式,常见的有以下几种:
1. [capture list] (params list) -> return type {function body}
格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值。
2. [capture list] (params list) {function body}
格式2省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型:
如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定
如果function body中没有return语句,则返回值为void类型。
3. [capture list] {function body}
格式3中省略了参数列表,类似普通函数中的无参函数。
捕获字句
捕获子句指定了哪些变量可以被捕获,以及捕获的形式(值还是引用)。捕获的是lambda表达式所在的封闭作用域中。
如果[]里为空,表示任何变量都不会传递给lambda表达式。
[=]表示默认按值传递,[&]表示默认按引用传递。[var]:var是变量名,前面可以添加&前缀,表示var变量按引用传递。
最简单的lambda表达式:
auto fun = [](){cout << "hello lambda"<<endl; }; //这里也可以使用auto变量
fun();
auto fun1 = [](double a)->double{ a += 1.1; return a;};
cout<<fun1(1.2)<<endl;
经典排序算法用lamdba表达式:
vector<OrderItemResult> order_item_list;
auto sort_by_position = [&ordered_pos](const OrderItemResult * r1, const OrderItemResult * r2) {
return ordered_pos.at(r1->order_item_id()) < ordered_pos.at(r2->order_item_id());
};
sort(order_item_list.begin(), order_item_list.end(), sort_by_position);