C++11简明入门教程培训

前言

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>

 

  1. 默认构造函数

thread() noexcept

一个空的std::thread执行对象

  1. 初始化构造函数

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);

  1. 拷贝构造函数

thread(const thread&) = delete;

拷贝构造函数被禁用,std::thread对象不可拷贝构造

  1. 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();

  1. 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;

  1. join() joinable()

创建线程执行线程函数,调用该函数会阻塞当前线程,直到线程执行完join才返回。

在线程内调用自上的join不允许会抛出异常

thread t1(threadFun);

t1.join() //阻塞等待

 

  1. detach()

detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。

 

  1. 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;

  1. hardware_concurrency()

获得逻辑处理器储量,返回值为int型

int coreNum = thread::hardware_concurrency();

  1. 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秒之后

  1. 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多了两个成员函数:

  1. 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;

}

上面的代码有三个注意事项:

  1. 在function_2中,在判断队列是否为空的时候,使用的是while(q.empty()),而不是if(q.empty()),这是因为wait()从阻塞到返回,不一定就是由于notify_one()函数造成的,还有可能由于系统的不确定原因唤醒(可能和条件变量的实现机制有关),这个的时机和频率都是不确定的,被称作伪唤醒,如果在错误的时候被唤醒了,执行后面的语句就会错误,所以需要再次判断队列是否为空,如果还是为空,就继续wait()阻塞。

  2. 在管理互斥锁的时候,使用的是std::unique_lock而不是std::lock_guard,而且事实上也不能使用std::lock_guard,这需要先解释下wait()函数所做的事情。可以看到,在wait()函数之前,使用互斥锁保护了,如果wait的时候什么都没做,岂不是一直持有互斥锁?那生产者也会一直卡住,不能够将数据放入队列中了。所以,wait()函数会先调用互斥锁的unlock()函数,然后再将自己睡眠,在被唤醒后,又会继续持有锁,保护后面的队列操作。而lock_guard没有lock和unlock接口,而unique_lock提供了。这就是必须使用unique_lock的原因。

  3. 使用细粒度锁,尽量减小锁的范围,在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可以用来实现共享所有权的概念。多个智能指针可以引用同一个对象,当最后一个智能指针销毁时,对象销毁。

 

 

 

需要注意的几点:

  1. 多线程

    1. 一个 shared_ptr 对象实体可被多个线程同时读取

    2. 两个 shared_ptr 对象实体可以被两个线程同时写入

    3. 多个线程读写同一个 shared_ptr 对象 不允许

    4. 所管理的对象的线程安全性由其本身决定

  2. 循环引用 weak_ptr

  3. 不要用一个原始指针初始化多个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");

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值