前言
C++11的11代表2011年出的标准。C++ 11引入了大量非常有用的特性,使代码更直观、安全、简洁、方便。像线程和锁的引入,使C++能够更容易的开发跨平台的程序。在新的软件产品开发中,强烈建议贯彻使用C++11以上标准。
std是什么
std:: 是个名称空间标示符,C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
#include<iostream>
int main()
{
std::cout<<"我喜欢C++";//输出一句话
std::cout<<std::endl;//换行
return 0;
}
C++11 std基本类型
#include <cstdint>
int main()
{
std::uint16_t n;
return 0;
}
有了这个类型之后没必要自己typedef了。
这个头文件的类型还有:
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
#define INT8_MIN (-127i8 - 1)
#define INT16_MIN (-32767i16 - 1)
#define INT32_MIN (-2147483647i32 - 1)
#define INT64_MIN (-9223372036854775807i64 - 1)
#define INT8_MAX 127i8
#define INT16_MAX 32767i16
#define INT32_MAX 2147483647i32
#define INT64_MAX 9223372036854775807i64
#define UINT8_MAX 0xffui8
#define UINT16_MAX 0xffffui16
#define UINT32_MAX 0xffffffffui32
#define UINT64_MAX 0xffffffffffffffffui64
新的关键字
auto
auto的原理就是根据后面的值,来自己推测前面的类型是什么。
auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。
示例:
std::vector<std::string> ve;
std::vector<std::string>::iterator it = ve.begin();
==》
auto it = ve.begin();
auto i = 0ULL;// i 的类型即为unsigned long long
auto不能推导引用;auto也不能推导const;
unsigned long long a = 0;
unsigned long long &b = a;
auto m = b;//m 是unsigned long long
auto & i = a;// 等价于unsigned long long & i = a;
const auto j = 0ULL;//等价于const unsigned long long & j = 0ULL;
decltype
获取一个表达式的数据类型,有时我们希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量(初始化可以用auto)
int getSize();
int main(void)
{
int tempA = 2;
/*1.dclTempA为int.*/
decltype(tempA) dclTempA;
/*2.dclTempB为int,对于getSize根本没有定义,但是程序依旧正常,因为decltype只做分析,并不调用getSize().*/
decltype(getSize()) dclTempB;
return 0;
}
与auto不同,decltype不会忽略引用和const
const int ci = 0, &cj = ci;
deltype(ci) x = 0; //x的数据类型是const int
decltype(cj) y = x; //y 的数据类型是const int&. y引用了x。
nullptr
以往我们使用NULL表示空指针。它实际上是个为0的int值。下面的代码会产生岐义:
void f(int i) {} // chose this one
void f(const char* s) {}
f(NULL);//编译器会分不清该使用哪个函数
改为如下即可
f(nullptr);
final
禁止虚函数被重写
class A {public:
virtual void f1() final {}};
class B : public A {
virtual void f1() {}};
编译会报错,达到设计目的
禁止类被继承
class A final {};
class B : public A {};
编译会报错,达到设计目的
override
显式声明重写
class A {public:
virtual void f1() const {}};
class B : public A {
virtual void f1() {}};
上面的代码在重写函数f1时不小心漏了const,但是编译器不会报错。因为它不知道你是要重写f1,而认为你是定义了一个新的函数。这样的情况也发生在基类的函数签名变化时,子类如果没有全部统一改过来,编译器也不能发现问题。
C++ 11引入了override声明,使重写更安全。
class B : public A {
virtual void f1() override {}};
删除构造函数=delete
以往,当我们需要隐藏构造函数时,可以把它声明为private成员
class A {
private:
A();
};
现在可以使用delete关键字
class A {
public:
A() = delete;
};
序列for循环
以前遍历vector一般是这么写的
for (std::vector<int>::const_iterator itr = v.begin(); itr != v.end(); ++itr) {
std::cout << *itr << std::endl;
}
这样写有两个缺点:
迭代器声明很冗长(用auto可以部分解决)
循环内部必须对迭代器解引用(主要是难看)
可以使用的新的遍历方式:
for (int i : v) {
std::cout << i << std::endl;
}
代码立马简洁了许多。但是要注意,这里每次循环,会对i进行一次拷贝。此处i是一个int值,拷贝不会造成问题,但是如果是一个class,我们就更希望用引用的方式进行遍历,一般写成:
std::vector<string> v = { "a", "b" };
for (auto& s : v) {
std::cout << s << std::endl;
}
用auto&即可以变成引用方式遍历,甚至还能在循环中改变它的值。也可以使用const auto&,只是一般没有必要。
变长参数模版(以及tuple)
C++03只有固定模板参数。C++11 加入新的表示法,允许任意个数、任意类别的模板参数,不必在定义时将参数的个数固定。我们在C++中都用过pair,pair可以使用make_pair构造,构造一个包含两种不同类型的数据的容器。比如,如下代码:
std::map::insert(std::make_pair<int,int>(0,0))
auto p = make_pair(1, "C++ 11");
由于在C++11中引入了变长参数模板,所以发明了新的数据类型:tuple,tuple是一个N元组,可以传入1个, 2个甚至多个不同类型的数据
auto t1 = make_tuple(1, 2.0, "C++ 11");
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});
这样就避免了从前的pair中嵌套pair的丑陋做法,使得代码更加整洁
另一个经常见到的例子是Print函数,在C语言中printf可以传入多个参数,在C++11中,我们可以用变长参数模板实现更简洁的Print
template<typename head, typename... tail>
void Print(Head head, typename... tail) {
cout<< head <<endl;
Print(tail...);
}
Print中可以传入多个不同种类的参数,如下:
Print(1, 1.0, "C++11");
tuple更多
tuple<const char*, int>tp = make_tuple(sendPack,nSendSize);
tp 等价于
struct A
{
char* p;
int len;
};
std::get<0>(tp);//A::p
std::get<1>(tp);//A::len
初始化列表 Initializer List
所有STL容器都支持初始化列表,如下:
std::vector<int> v = { 1, 2, 3 };
std::list<int> l = { 1, 2, 3 };
std::set<int> s = { 1, 2, 3 };
std::map<int, std::string> m = { {1, "a"}, {2, "b"} };
在自定义class上支持初始化列表
#include <initializer_list>
class A {
public:
A(const std::initializer_list<int>& items)
: m_items(items)
{}
private:
std::vector<int> m_items;
};
A a1 = { 1, 2, 3 };// 或者
A a2{ 1, 2, 3 };
统一的初始化方法 Uniform Initialization
可以统一使用大括号{}进行初始化。对构造函数的选择的优先级如下:
class A {
public:
// first choice
A(const std::initializer_list<int>& v) : age(*v.begin())
{}
// second choice
A(int age) : age(age)
{}
// third choice
int age;
};
A a{ 5 };
定义成员初始值
当我们为一个class增加成员变量时,要注意在所有构造函数中都对它进行初始化(除非这个成员的默认构造函数就满足我们的要求)。虽然C++ 11允许构造函数相互调用,但至少该成员变量的声明和初始化是分开写的,导致后者经常被遗忘。现在C++ 11可以在声明成员变量的时直接赋初始值。
class A {
public:
int m = 1;
};
struct B
{
int m = 2;
}
这个初始化的动作会在所有构造函数之前执行,可以理解为这些初始值会被自动放到初始化列表。如果初始化列表也有个初始化,则选用初始化列表的值。
class A {
public:
A() : m(2)
{
}
int m = 1; // 这个1被忽略
};
lambda表达式
概述
C++ 11 中的 Lambda 表达式用于定义并创建匿名的函数对象,以简化编程工作。
Lambda 的语法形式如下:
[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}
可以看到,Lambda 主要分为五个部分:[函数对象参数]、(操作符重载函数参数)、mutable 或 exception 声明、-> 返回值类型、{函数体}
语法分析
[函数对象参数]
标识一个 Lambda 表达式的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义 Lambda 为止时 Lambda 所在作用范围内可见的局部变量(包括 Lambda 所在类的 this)。函数对象参数有以下形式:
-
空。没有任何函数对象参数。
-
=。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
-
&。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。
-
this。函数体内可以使用 Lambda 所在类中的成员变量、成员函数。
-
a。将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。
-
&a。将 a 按引用进行传递。
-
[a,&b]。将 a 按值传递,b 按引用进行传递。
-
[=,&a,&b]。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
-
[&,a,b]。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
(操作符重载函数参数)
标识重载的 () 操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。
mutable 或 exception 声明
这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。
-> 返回值类型
标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
{函数体}
标识函数的实现,这部分不能省略,但函数体可以为空。
示例1 最简单lambda以及调用
auto a = []() { };//定义
a();//调用线程
示例2 常见lambda定义
[] (int x, int y) { return x + y; } // 隐式返回类型
[] (int& x) { ++x; } // 没有 return 语句 -> Lambda 函数的返回类型是 'void'
[] () { ++global_x; } // 没有参数,仅访问某个全局变量
[] { ++global_x; } // 与上一个相同,省略了 (操作符重载函数参数)
示例3
std::vector<int> some_list;
int total = 0;
for (int i = 0; i < 5; ++i)
some_list.push_back(i);
auto fn = [&total](int x)
{
total += x;
};
std::for_each(begin(some_list), end(some_list), [&total](int x)
{
total += x;
});
此例计算 list 中所有元素的总和。变量 total 被存为 Lambda 函数闭包的一部分。因为它是栈变量(局部变量)total 引用,所以可以改变它的值。
示例4
std::vector<int> some_list;
std::vector<int> some_list;
int total = 0;
int value = 5;
std::for_each(begin(some_list), end(some_list), [&, value, this](int x)
{
total += x * value * this->some_func();
});
此例此例中 total 会存为引用, value 则会存一份值拷贝。对 this 的捕获比较特殊,它只能按值捕获。this 只有当包含它的最靠近它的函数不是静态成员函数时才能被捕获。对 protect 和 private 成员来说,这个 Lambda 函数与创建它的成员函数有相同的访问控制。如果 this 被捕获了,不管是显式还是隐式的,那么它的类的作用域对 Lambda 函数就是可见的。访问 this 的成员不必使用 this-> 语法,可以直接访问。
示例5 在线程中使用
std::thread t([this](int i) { },1);
t.join();
示例6 在类中使用
class Type
{
public:
void start()
{
m_thread = std::thread([this]() {m_n = 2; fun(); });//this代表可以读写类成员变量和函数
m_thread.join();
}
private:
int m_n = 1;
std::thread m_thread;
void fun(){}
};
void main()
{
Type type;
type.start();
}
示例7 在QT中使用
connect(btnPrint, &QPushButton::clicked, [this] { //do something });
示例8
int data[6] = { 3, 4, 12, 2, 1, 6 };
vector<int> testdata;
testdata.insert(testdata.begin(), data, data + 6);
std::sort(testdata.begin(), testdata.end(), [](int a, int b){ return a > b; });
多线程
C++11引入了thread类,大大降低了多线程使用的复杂度,原先使用多线程只能用系统的API,无法解决跨平台问题,一套代码平台移植,对应多线程代码也必须要修改。现在在C++11中只需使用语言层面的thread可以解决这个问题。
所需头文件<thread>
-
默认构造函数
thread() noexcept
一个空的std::thread执行对象
-
初始化构造函数
template<class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args);
创建std::thread执行对象,线程调用threadFun函数,函数参数为args
void threadFun(int a)
{
cout << "this is thread fun !" << endl;
}
thread t1(threadFun, 2);
-
拷贝构造函数
thread(const thread&) = delete;
拷贝构造函数被禁用,std::thread对象不可拷贝构造
-
Move构造函数
thread(thread&& x)noexcept
调用成功原来x不再是std::thread对象
void threadFun(int& a)
{
cout << "this is thread fun !" << endl;
}
int value = 2;
thread t1(threadFun, std::ref(value));
thread t2(std::move(t1));
t2.join();
-
get_id()
thread t1(threadFun);
thread::id threadId = t1.get_id();
cout << "线程ID:" << threadId << endl;
//threadId转换成整形值,所需头文件<sstream>
ostringstream oss;
oss << t1.get_id();
string strId = oss.str();
unsigned long long tid = stoull(strId);
cout << "线程ID:" << tid << endl;
-
join() joinable()
创建线程执行线程函数,调用该函数会阻塞当前线程,直到线程执行完join才返回。
在线程内调用自上的join不允许会抛出异常
thread t1(threadFun);
t1.join() //阻塞等待
-
detach()
detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。
-
swap()
交换两个线程对象
thread t1(threadFun1);
thread t2(threadFun2);
cout << "线程1的ID:" << t1.get_id() << endl;
cout << "线程2的ID:" << t2.get_id() << endl;
t1.swap(t2);
cout << "线程1的ID:" << t1.get_id() << endl;
cout << "线程2的ID:" << t2.get_id() << endl;
-
hardware_concurrency()
获得逻辑处理器储量,返回值为int型
int coreNum = thread::hardware_concurrency();
-
std::this_thread::sleep_for() sleep_until()
阻塞当前线程一段时间
this_thread::sleep_for(chrono::nanoseconds(1000));//阻塞当前线程1000纳秒
this_thread::sleep_for(chrono::microseconds(1000));//阻塞当前线程1000微妙
this_thread::sleep_for(chrono::milliseconds(1000));//阻塞当前线程1000毫秒
this_thread::sleep_for(chrono::seconds(20)+ chrono::minutes(1));//阻塞当前线程1分钟20秒
this_thread::sleep_for(chrono::hours(1));//阻塞当前线程1小时
阻塞当前线程直到某个时间点
chrono::system_clock::time_point until = chrono::system_clock::now();
until += chrono::seconds(5);
this_thread::sleep_until(until);//阻塞到5秒之后
-
std::this_thread::yield()
放弃当前线程的时间片,使CPU重新调度以便其它线程执行
bool g_ready;
void waitReady()
{
while (!g_ready) {
this_thread::yield();
}
}
int main()
{
thread t(waitReady);
t.detach();
string inputStr;
while (cin >> inputStr) {
if (inputStr == "hello"){
break;
}
}
g_ready = true;
system("pause");
}
示例1 不带参数线程
void fun()
{
cout << "fun" << endl;
}
int main()
{
std::thread t(fun);
t.join();//等待线程结束
return 0;
}
示例2类成员函数做为线程入口
class Greet
{
const char *owner = "Greet";
public:
void SayHello(const char *name) {
std::cout << "Hello " << name << " from " << this->owner << std::endl;
}
};
int main()
{
Greet greet;
std::thread thread(&Greet::SayHello, &greet, "C++11");
thread.join();
return 0;
}
示例3创建一个const引用参数的线程
void fun(const std::string& s)
{
}
int start()
{
std::string s = "aaa";
std::thread t(fun, std::cref(s));
//t.detach();
t.join();
return 0;
}
std::call_once
就是保证函数或者一些代码段在并发或者多线程的情况下,始终只会被执行一次。比如单例模式的初始化。
std::once_flag onceflag_global_init;
void global_init() { cout << "global_init\n"; }
void thread1()
{
std::call_once(onceflag_global_init, global_init);
cout << "thread1 work...\n";
}
void thread2()
{
std::call_once(onceflag_global_init, global_init);
cout << "thread2 work...\n";
}
int main()
{
std::thread(thread1).detach();
std::thread(thread2).detach();
return 0;
}
互斥锁
std::mutex 普通的互斥锁, 不能递归使用
std::timed_mutex 定时互斥锁,不能递归使用。std::time_mutex比std::mutex多了两个成员函数:
-
try_lock_for():函数参数表示一个时间范围,在这一段时间范围之内线程如果没有获得锁则保持阻塞;如果在此期间其他线程释放了锁,则该线程可获得该互斥锁;如果超时(指定时间范围内没有获得锁),则函数调用返回false。
B、try_lock_until():函数参数表示一个时刻,在这一时刻之前线程如果没有获得锁则保持阻塞;如果在此时刻前其他线程释放了锁,则该线程可获得该互斥锁;如果超过指定时刻没有获得锁,则函数调用返回false。
std::recursive_mutex 该类表示递归互斥锁。递归互斥锁可以被同一个线程多次加锁,以获得对互斥锁对象的多层所有权
std::recursive_timed_mutex:带定时的递归互斥锁
示例 1
std::deque<int> q;
std::mutex mu;
void function_1() {//生产者
int count = 10;
while (count > 0) {
mu.lock();
q.push_front(count);
mu.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
count--;
}}
void function_2() //消费者
{
int data = 0;
while ( data != 1)
{
mu.lock();
if (!q.empty()) {
data = q.back();
q.pop_back();
mu.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
} else {
mu.unlock();
}
}
}
int main()
{
std::thread t1(function_1);
std::thread t2(function_2);
t1.join();
t2.join();
return 0;
}
std::lock_guard<>区域锁
std::mutex m_mutex_cv;
{
std::lock_guard<mutex> lk(m_mutex_cv);
s += "a";
}
相当于
{
m_mutex_cv.lock();
s += "a";
m_mutex_cv.unlock();
}
std::unique_lock<>
工作中,一般lock_guard(推荐使用);lock_guard取代了mutex的lock()和unlock();
unique_lock比lock_guard灵活很多,效率上差一点,内存占用多一点。
unique_lock提供了lock, unlock, try_lock等接口.
std::mutex _mu;
void shared_print(string& msg, int id)
{
std::unique_lock<std::mutex> guard(_mu);
//do something 1 使用到msg
guard.unlock(); //临时解锁
//do something 2 200ms
guard.lock(); //继续上锁
// do something 3
cout << msg << id << endl;
// 结束时析构guard会临时解锁
// 这句话可要可不要,不写,析构的时候也会自动执行
// guard.ulock();
}
std::condition_variable条件变量
通知
std::deque<int> q;
std::mutex mu;
std::condition_variable cond;
void function_1() //生产者
{
//int count = 10;
while (1)
{
std::unique_lock<std::mutex> locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one(); // Notify one waiting thread, if there is one.
std::this_thread::sleep_for(std::chrono::seconds(1));
// count--;
}
}
void function_2() //消费者
{
int data = 0;
while ( data != 1)
{
std::unique_lock<std::mutex> locker(mu);
while(q.empty())
cond.wait(locker); // Unlock mu and wait to be notified
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
}
}
int main()
{
std::thread t1(function_1);
std::thread t2(function_2);
std::thread t3(function_2);
t1.join();
t2.join();
return 0;
}
上面的代码有三个注意事项:
-
在function_2中,在判断队列是否为空的时候,使用的是while(q.empty()),而不是if(q.empty()),这是因为wait()从阻塞到返回,不一定就是由于notify_one()函数造成的,还有可能由于系统的不确定原因唤醒(可能和条件变量的实现机制有关),这个的时机和频率都是不确定的,被称作伪唤醒,如果在错误的时候被唤醒了,执行后面的语句就会错误,所以需要再次判断队列是否为空,如果还是为空,就继续wait()阻塞。
-
在管理互斥锁的时候,使用的是std::unique_lock而不是std::lock_guard,而且事实上也不能使用std::lock_guard,这需要先解释下wait()函数所做的事情。可以看到,在wait()函数之前,使用互斥锁保护了,如果wait的时候什么都没做,岂不是一直持有互斥锁?那生产者也会一直卡住,不能够将数据放入队列中了。所以,wait()函数会先调用互斥锁的unlock()函数,然后再将自己睡眠,在被唤醒后,又会继续持有锁,保护后面的队列操作。而lock_guard没有lock和unlock接口,而unique_lock提供了。这就是必须使用unique_lock的原因。
-
使用细粒度锁,尽量减小锁的范围,在notify_one()的时候,不需要处于互斥锁的保护范围内,所以在唤醒条件变量之前可以将锁unlock()。
还可以将cond.wait(locker);换一种写法,wait()的第二个参数可以传入一个函数表示检查条件,这里使用lambda函数最为简单,如果这个函数返回的是true,wait()函数不会阻塞会直接返回,如果这个函数返回的是false,wait()函数就会阻塞着等待唤醒,如果被伪唤醒,会继续判断函数返回值。
void function_2()
{
int data = 0;
while ( data != 1) {
std::unique_lock<std::mutex> locker(mu);
cond.wait(locker,[](){ return !q.empty();});// Unlock mu and wait to be notified
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
}
}
std::promise std::future
通知 只能通知一次
void read(std::future<std::string> *future)
{
// future会一直阻塞,直到有值到来
std::cout << future->get() << std::endl;
}
int main()
{
// promise 相当于生产者
std::promise<std::string> promise;
// future 相当于消费者, 右值构造
std::future<std::string> future = promise.get_future();
// 另一线程中通过future来读取promise的值
std::thread thread(read, &future);
// 让read等一会儿:)
std::this_thread::sleep_for(seconds(1));
//
promise.set_value("hello future");
// 等待线程执行完成
thread.join();
return 0;
}
atomic
可以使用它定义一个原子类型。
成员函数 |
说明 |
store |
原子地以非原子对象替换原子对象的值 |
load |
|
特化成员函数 |
说明 |
fetch_add |
原子地将参数加到存储于原子对象的值,并返回先前保有的值 |
fetch_sub |
原子地进行参数和原子对象的值的逐位与,并获得先前保有的值 |
fetch_or |
原子地进行参数和原子对象的值的逐位或,并获得先前保有的值 |
fetch_xor |
原子地进行参数和原子对象的值的逐位异或,并获得先前保有的值 |
operator++ |
令原子值增加一 |
operator++(int) |
令原子值增加一 |
operator– |
令原子值减少一 |
operator–(int) |
令原子值减少一 |
示例1:
// 定一个int64_t的原子类型
std::atomic<int64_t> value;
// atomic提供的特化成员函数,已经重载了++运算符
// 所以该操作时原子的
value++;
int64_t x = value.load(std::memory_order_relaxed);
int64_t x = 10;
value.store(x,std::memory_order_relaxed)
示例2:
std::atomic<std::uint32_t> sum = 0;
thread t1([&]() {for (int i = 0; i < 1000000; i++) ++sum; });
thread t2([&]() {for (int i = 0; i < 1000000; i++) --sum; });
t1.join();
t2.join();
//输出结果肯定为0
智能指针
指针是C++的一个重要却又充满了麻烦的特性。使用指针的一个理由是在作用域以外使用引用语义。但是,指针的生命期和所指对象的生命期的确认是十分恼人的一件事情。
内存忘记释放
内存重复释放
内存提前释放
内存清理代码臃肿
所以C++11 提供了智能指针用于解决这些问题,shared_ptr weak_ptr unique_ptr auto_ptr(废弃)。
智能指针是一个用来存储指针的类,具有RAII特性(Resource Acquisition Is Initialization)
std::unique_ptr
unique_ptr单向传递。一个unique_ptr"拥有”他所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。uniptr_ptr表达的是一种独占的思想。
auto TObject = new Type()
std::unique_ptr<Type> p(TObject);
//p = std::make_unique<Type>();//初始化//p生命周期结束自动释放持有的内存
//auto q = p;//无法编译,不能operator=赋值
class Type
{
public:
Type() { cout << "Type::Type()" << endl; }
Type(std::string s) :m_s(s) { cout << "Type(std::string s)" << endl; }
~Type() { cout << "Type::~Type()" << m_s.c_str() << endl; }
void fun() { cout << "fun();" << endl; }
private:
std::string m_s = "initstr";
};
int main()
{
std::unique_ptr<Type> p;
p = std::make_unique<Type>();//初始化//p生命周期结束自动释放持有的内存
//auto q = p;//无法编译,不能operator=赋值
p.reset(new Type("aaa"));//重置//只能从*重置//旧的内存块释放
std::unique_ptr<Type> p1 = std::move(p);//转移//p生命周期结束不释放内存
Type* type1 = p1.get();//得到原始指针
std::unique_ptr<Type> p2(new Type);//使用new初始化
Type* type2 = p2.release();//提前释放并返回原始指针
std::unique_ptr<Type[]> parray(new Type[3]);
parray[0].fun();//当内置类型为[]数组时,不提供operator*和operator->运算符
auto del_er = [](Type* pt) { cout << "will delete\n"; delete pt; };//删除器函数
std::unique_ptr<Type, decltype(del_er)> p3(new Type(), del_er);//指定删除器
auto p4 = std::make_unique<Type>();
auto p5 = std::make_unique<Type>("p5");
p4.swap(p5);//交换指针
std::unique_ptr<Type> p6;
auto p7 = std::make_unique<Type>();
bool b1 = (bool)p6;//operator bool//false
bool b2 = (bool)p7;//operator bool//true
return 0;
}
典型用法
bool fun()
{
std::unique_ptr<char[]> parray(new char[1024]);
if (fun1()== false){
return false;
}
if (fun2()== false){
return false;
}
if (fun3()== false){
return false;
}
}
std::shared_ptr
shared_ptr可以用来实现共享所有权的概念。多个智能指针可以引用同一个对象,当最后一个智能指针销毁时,对象销毁。
需要注意的几点:
-
多线程
-
一个 shared_ptr 对象实体可被多个线程同时读取
-
两个 shared_ptr 对象实体可以被两个线程同时写入
-
多个线程读写同一个 shared_ptr 对象 不允许
-
所管理的对象的线程安全性由其本身决定
-
-
循环引用 weak_ptr
-
不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存
std::weak_ptr
weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。
using namespace std;
class Type
{
public:
Type() { cout << "Type::Type()" << endl; }
Type(std::string s) :m_s(s) { cout << "Type(std::string s)" << endl; }
~Type() { cout << "Type::~Type()" << m_s.c_str() << endl; }
void fun() { cout << "fun();" << endl; }
private:
std::string m_s = "initstr";
};
int main()
{
std::shared_ptr<Type> p1 = std::make_shared<Type>();
std::weak_ptr<Type> p2(p1);//weak_ptr从shared_ptr构造
std::shared_ptr<Type> p3 = p2.lock();//创建一个shared_ptr,并指向新创建的内存
p1 = nullptr;//释放p1原有内存块
p2.expired();//false//仍然未过期过期
return 0;
}
enable_shared_from_this
使用场合
当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。
1.为何不直接传递this指针
使用智能指针的初衷就是为了方便资源管理,如果在某些地方使用智能指针,某些地方使用原始指针,很容易破坏智能指针的语义,从而产生各种错误。
2.可以直接传递share_ptr<this>么?
答案是不能,因为这样会造成2个非共享的share_ptr指向同一个对象,未增加引用计数导对象被析构两次
struct Good : std::enable_shared_from_this<Good> // 注意:继承
{
public:
std::shared_ptr<Good> getptr() {
return shared_from_this();
}
~Good() { std::cout << "Good::~Good() called" << std::endl; }
};
int main()
{
// 大括号用于限制作用域,这样智能指针就能在system("pause")之前析构
{
std::shared_ptr<Good> gp1(new Good());
std::shared_ptr<Good> gp2 = gp1->getptr();
// 打印gp1和gp2的引用计数
std::cout << "gp1.use_count() = " << gp1.use_count() << std::endl;
std::cout << "gp2.use_count() = " << gp2.use_count() << std::endl;
}
system("pause");
}