C++秋招记录(六)——C++11

一、c++11一些特性

1、std::move

功能:返回传入参数的右值引用。
优点:调用此函数不会引起任何数据争用

  • 通过将对象传递给此函数,可以获得引用该对象的右值。 标准库的许多组件都实现了移动语义,允许直接转移对象的资产和属性的所有权,而在参数为右值时无需复制它们。
  • 要注意-在标准库中-移动意味着从中移出的对象处于有效但未指定的状态。 这意味着在执行此操作后,仅应销毁移出对象的值或为其分配新值; 否则访问它会产生未指定的值。

右值引用

  • 左值引用:type &引用名 = 左值表达式;
  • 右值引用:type &&引用名 = 右值表达式;
  • 通常,右值是其地址无法通过解引用获得的值,因为它们本质上是临时的(例如,函数或显式构造函数返回的值)。

std::move使用

	// move example
	#include <utility>      // std::move
	#include <iostream>     // std::cout
	#include <vector>       // std::vector
	#include <string>       // std::string
	 
	int main () {
	  std::string foo = "foo-string";
	  std::string bar = "bar-string";
	  std::vector<std::string> myvector;
	 
	  myvector.push_back (foo);                    // copies
	  myvector.push_back (std::move(bar));         // moves
	 
	  std::cout << "myvector contains:";
	  for (std::string& x:myvector) std::cout << ' ' << x;
	  std::cout << '\n';
	  
	  std::cout << "foo:"<<foo<<'\n';
	  std::cout << "bar:"<<bar<<std::endl;
	 
	  return 0;
	}

输出结果:

	myvector contains: foo-string bar-string
	foo:foo-string
	bar:

2、C++11的std::function和std::bind用法详解?

C++中有如下几种可调用对象:函数、函数指针、lambda表达式、bind对象、函数对象。其中,lambda表达式和bind对象是C++11标准中提出的(bind机制并不是新标准中首次提出,而是对旧版本中bind1st和bind2st的合并)

lambda表达式

  • lambda介绍
    匿名:没有一个确定的名称
    函数:lambda不属于一个特定的类,但是却有参数列表、函数主体、返回类型、异常列表
    传递:可以作为参数传递给方法、或者存储在变量中
    优点:lambda表达式就是一段可调用的代码,主要适合于只用到一两次的简短代码段,不需要写很多模板代码。由于lambda是匿名的,所以保证了其不会被不安全的访问:

  • 如下例子:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    bool cmp(int a, int b)
    {
        return  a < b;
    }
    
    int main()
    {
        vector<int> myvec{ 3, 2, 5, 7, 3, 2 };
        vector<int> lbvec(myvec);
    
        sort(myvec.begin(), myvec.end(), cmp); // 旧式做法
        cout << "predicate function:" << endl;
        for (int it : myvec)
            cout << it << ' ';
        cout << endl;
    
        sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });   // Lambda表达式
        cout << "lambda expression:" << endl;
        for (int it : lbvec)
            cout << it << ' ';
    }
    
  • 定义形式
    [capture list] (params list) mutable exception-> return type { function body }
    参数解释:capture list:捕获外部变量列表、params list:形参列表、mutable指示符:用来说用是否可以修改捕获的变量、exception:异常设定、return type:返回类型、function body:函数体
    几种常见类型在这里插入图片描述

    • (1)capture list可以是**=或&,表示{}中用到**的、定义在{}外面的变量在{}中是否允许被改变。=表示不允许,&表示允许。当然[]在{}中也可以不使用定义在外面的变量,也可以混合使用。
    • (2) 如果没有参数,空的圆括号()可以省略.返回值也可以省略,也可以使用隐式返回类型
        []  //未定义变量.试图在Lambda内使用任何外部变量都是错误的.
    	[x, &y]   //x 按值捕获, y 按引用捕获.
    	[&]       //用到的任何外部变量都隐式按引用捕获
    	[=]       //用到的任何外部变量都隐式按值捕获
    	[&, x]    //x显式地按值捕获. 其它变量按引用捕获
    	[=, &z]   //z按引用捕获. 其它变量按值捕获
    
    • (3)修改捕获变量
      在Lambda表达式中,如果以传值方式捕获外部变量,则函数体中不能修改该外部变量,否则会引发编译错误。使用mutable关键字,该关键字用以说明表达式体内的代码可以修改值捕获的变量。
    int main()
    {
        int a = 123;
        auto f = [a]()mutable { cout << ++a; }; // 不会报错
        cout << a << endl; // 输出:123
        f(); // 输出:124
    }
    
  • (4)Lambda表达式的参数限制条件:

    • 参数列表中不能有默认参数
    • 不支持可变参数
    • 所有参数必须有参数名
  • 用例说明:

    //一般使用
    [](int x, int y) -> int { int z = x + y; return z; } //显示指定返回类型
    [](int x, int y) { return x + y; } // 隐式返回类型
    [](int& x) { ++x; }   // 没有return语句 -> lambda 函数的返回类型是'void'
    []() { ++global_x; }  // 没有参数,仅访问某个全局变量
    []{ ++global_x; }     // 与上一个相同,省略了()
    //返回值为function
    auto f = [](int x, int y) { return x + y; };  
    std::cout << f(x, y) << std::endl;            //调用lambda表达式
    //嵌套使用
    auto gFunc = [](int x) -> function<int(int)> { return [=](int y) { return x + y; }; };  
    auto lFunc = gFunc(4);
    

std::function

std::function是一个可调用对象包装器,是一个类模板(只限定了返回值、参数列表),可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。
定义function的一般形式:

# include <functional>
std::function<函数返回值(参数列表)>

eg.

# include <iostream>
# include <functional>

typedef std::function<int(int, int)> comfun;

// 普通函数
int add(int a, int b) { return a + b; }

// lambda表达式
auto mod = [](int a, int b){ return a % b; };

// 函数对象类
struct divide{
    int operator()(int denominator, int divisor){
        return denominator/divisor;
    }
};

int main(){
	comfun a = add;
	comfun b = mod;
	comfun c = divide();
    std::cout << a(5, 3) << std::endl;
    std::cout << b(5, 3) << std::endl;
    std::cout << c(5, 3) << std::endl;
}
  • std::function的作用:
  • (1)std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象,简化调用;
  • (2)std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(如:函数指针这类可调用实体,是类型不安全的)。std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。

std::bind

std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:

  • 将可调用对象和其参数绑定成一个仿函数;
  • 只绑定部分参数,减少可调用对象传入的参数。

调用bind的一般形式:

auto newCallable = std::bind(callable, arg_list);

细节:
1、std::placeholders::是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。
2、bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的;bind对于预先绑定的函数参数是通过值传递的。

#include <iostream>
#include <functional>

class A {
public:
    void fun_3(int k,int m) {
        std::cout << "print: k = "<< k << ", m = " << m << std::endl;
    }
};

void fun_1(int x,int y,int z) {
    std::cout << "print: x = " << x << ", y = " << y << ", z = " << z << std::endl;
}

void fun_2(int &a,int &b) {
    ++a;
    ++b;
    std::cout << "print: a = " << a << ", b = " << b << std::endl;
}

int main(int argc, char * argv[]) {
    //f1的类型为 function<void(int, int, int)>
    auto f1 = std::bind(fun_1, 1, 2, 3); 					//表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
    f1(); 													//print: x=1,y=2,z=3

    auto f2 = std::bind(fun_1, std::placeholders::_1, std::placeholders::_2, 3);
    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f2 的第一,二个参数指定
    f2(1, 2);												//print: x=1,y=2,z=3
 
    auto f3 = std::bind(fun_1, std::placeholders::_2, std::placeholders::_1, 3);
    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f3 的第二,一个参数指定
    //注意: f2  和  f3 的区别。
    f3(1, 2);												//print: x=2,y=1,z=3

    int m = 2;
    int n = 3;
    auto f4 = std::bind(fun_2, std::placeholders::_1, n); //表示绑定fun_2的第二个参数为n,fun_2的第一个参数由调用f4的第一个参数(_1)指定 。
    f4(m); 													//print: a=3,b=4
    std::cout << "m = " << m << std::endl;					//m=3  说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的,如m
    std::cout << "n = " << n << std::endl;					//n=3  说明:bind对于预先绑定的函数参数是通过值传递的,如n
    
    A a;
    //f5的类型为 function<void(int, int)>
    auto f5 = std::bind(&A::fun_3, &a, std::placeholders::_1, std::placeholders::_2); //使用auto关键字
    f5(10, 20);												//调用a.fun_3(10,20),print: k=10,m=20

    std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
    fc(10, 20);   											//调用a.fun_3(10,20) print: k=10,m=20 

    return 0; 
}

二、智能指针

  • 为什么要使用智能指针:
    智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
    智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
  • 智能指针定义:是一个作用是资源管理的类,它是你在堆栈上声明的类模板,并可通过使用指向某个堆分配的对象的原始指针进行初始化(RAII)。

1、auto_ptr(c++98的方案,cpp11已经抛弃)

采用所有权模式。

std::auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.));
std::auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题

2、unique_ptr

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。
采用所有权模式,还是上面那个例子

std::unique_ptr<string> p3 (new string ("auto"));   //#4
std::unique_ptr<string> p4;                       //#5
p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1;                                      // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You"));   // #2 allowed

3、share_ptr(强引用)

  • shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针

  • shared_ptr的使用:shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

  • 初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针

  • shared_ptr有如下几种初始化方式:

    1. 裸指针直接初始化,但不能通过隐式转换来构造,因为shared_ptr构造函数被声明为explicit;
    2. 允许移动构造,也允许拷贝构造;
    3. 通过make_shared构造,在C++11版本中就已经支持了。
    #include <iostream>
    #include <memory>
    
    class Frame {};
    
    int main()
    {
      std::shared_ptr<Frame> f(new Frame());              // 裸指针直接初始化
      std::shared_ptr<Frame> f1 = new Frame();            // Error,explicit禁止隐式初始化
      std::shared_ptr<Frame> f2(f);                       // 拷贝构造函数
      std::shared_ptr<Frame> f3 = f;                      // 拷贝构造函数
      f2 = f;                                             // copy赋值运算符重载
      std::cout << f3.use_count() << " " << f3.unique() << std::endl;
    
      std::shared_ptr<Frame> f4(std::move(new Frame()));        // 移动构造函数
      std::shared_ptr<Frame> f5 = std::move(new Frame());       // Error,explicit禁止隐式初始化
      std::shared_ptr<Frame> f6(std::move(f4));                 // 移动构造函数
      std::shared_ptr<Frame> f7 = std::move(f6);                // 移动构造函数
      std::cout << f7.use_count() << " " << f7.unique() << std::endl;
    
      std::shared_ptr<Frame[]> f8(new Frame[10]());             // Error,管理动态数组时,需要指定删除器
      std::shared_ptr<Frame> f9(new Frame[10](), std::default_delete<Frame[]>());
    
      auto f10 = std::make_shared<Frame>();               // std::make_shared来创建
    
      return 0;
    }
    
    
  • 成员函数:
    use_count 返回引用计数的个数
    unique 返回是否是独占所有权( use_count 为 1)
    swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
    reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
    get 返回内部对象指针(返回shared_ptr中保存的裸指针)由于已经重载了()方法, 因此和直接使用对象是一样的.

    std::shared_ptr<int> sp(new int(1)); 
    //sp 与 sp.get()是等价的
    

shared_ptr内存模型

在这里插入图片描述
shared_ptr包含了一个指向对象的指针和一个指向控制块的指针。每一个由shared_ptr管理的对象都有一个控制块,它除了包含强引用计数、弱引用计数之外,还包含了自定义删除器的副本和分配器的副本以及其他附加数据。

控制块的创建规则:

  • std::make_shared总是创建一个控制块;
  • 从具备所有权的指针出发构造一个std::shared_ptr时,会创建一个控制块(如std::unique_ptr转为shared_ptr时会创建控制块,因为unique_ptr本身不使用控制块,同时unique_ptr置空);
  • 当std::shared_ptr构造函数使用裸指针作为实参时,会创建一个控制块。这意味从同一个裸指针出发来构造不止一个std::shared_ptr时会创建多重的控制块,也意味着对象会被析构多次。如果想从一个己经拥有控制块的对象出发创建一个std::shared_ptr,可以传递一个shared_ptr或weak_ptr而非裸指针作为构造函数的实参,这样则不会创建新的控制块。
  • 因此,尽可能避免将裸指针传递给一个std::shared_ptr的构造函数,常用的替代手法是使用std::make_shared

share_ptr是否线程安全

  1. 同一个shared_ptr被多个线程“读”是安全的。
  2. 同一个shared_ptr被多个线程“写”是不安全的。
  3. 共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的。

Examples

shared_ptr<int> p(new int(42));
//Code Example 4. Reading a shared_ptr from two threads,线程安全
// thread A
shared_ptr<int> p2(p); // reads p
// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe

//Code Example 5. Writing different shared_ptr instances from two threads,线程安全
// thread A
p.reset(new int(1912)); // writes p
// thread B
p2.reset(); // OK, writes p2

//Code Example 6. Reading and writing a shared_ptr from two threads,线程不安全
// thread A
p = p3; // reads p3, writes p
// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write

//Code Example 7. Reading and destroying a shared_ptr from two threads,线程不安全
// thread A
p3 = p2; // reads p2, writes p3
// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"

//Code Example 8. Writing a shared_ptr from two threads,线程不安全
// thread A
p3.reset(new int(1));
// thread B
p3.reset(new int(2)); // undefined, multiple writes

4、weak_ptr(弱引用)

  • 定义:weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象.进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。

  • 目的:

    • weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。
    • 配合shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr或另一个weak_ptr 对象构造,它的构造和析构不会引起引用记数的增加或减少。
    • 它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr
  • 初始化方式
    通过shared_ptr直接初始化,也可以通过隐式转换来构造;
    允许移动构造,也允许拷贝构造。

    #include <iostream>
    #include <memory>
    
    class Frame {};
    
    int main()
    {
      std::shared_ptr<Frame> f(new Frame());
      std::weak_ptr<Frame> f1(f);                     // shared_ptr直接构造
      std::weak_ptr<Frame> f2 = f;                    // 隐式转换
      std::weak_ptr<Frame> f3(f1);                    // 拷贝构造函数
      std::weak_ptr<Frame> f4 = f1;                   // 拷贝构造函数
      std::weak_ptr<Frame> f5;
      f5 = f;                                         // 拷贝赋值函数
      f5 = f2;                                        // 拷贝赋值函数
      std::cout << f.use_count() << std::endl;        // 1
    
      return 0;
    }
    
    
  • 常用操作
    w.user_count():返回weak_ptr的强引用计数;
    w.reset(…):重置weak_ptr。

推荐优秀博主:
https://yngzmiao.blog.csdn.net/article/details/105903979
参考博客:
https://www.jianshu.com/p/cb3e574eee5f
https://blog.csdn.net/qq_38410730/article/details/103637778
https://www.cnblogs.com/DswCnblog/p/5629165.html
https://www.cnblogs.com/pzhfei/archive/2013/01/14/lambda_expression.html
https://blog.csdn.net/a13935302660/article/details/96437703

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值