C++11新特性

C++11新特性

编码文章call10242025-01-14 10:30:0922A+A-

1、智能指针

2、Lambda表达式

3、线程库

4、原子操作

5、统一的列表初始化 {}

6、右值引用和移动构造

7、引入nullptr指针

8、类型推导 auto 和 decltype


智能指针:

智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源,常用的执政指针如下:

shared_ptr:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。调用 reset() 函数之后,new 对象的引用计数就会减 1,当前智能指针对象被赋值为nullptr;

unique_ptr:独占指针,采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。

weak_ptr :弱指针;指向 shared_ptr 管理的 new 对象,却没有该对象的所有权,即无法通过 weak_ptr 对象管理 new 对象。最常见的用法:1、shared_ptr 对象的有效性;2、解决循环引用导致的内存泄露的问题。

auto_ptr:已被弃用,不做讲解

如果手写指针指针类,需要实现的函数:

智能指针是一个数据类型,一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。

除了指针对象外,我们还需要一个引用计数的指针设定对象的值,并将引用计数计为1,需要一个构造函数。新增对象还需要一个构造函数,析构函数负责引用计数减少和释放内存。

通过覆写赋值运算符,才能将一个旧的智能指针赋值给另一个指针,同时旧的引用计数减1,新的引用计数加1

一个构造函数、拷贝构造函数、复制构造函数、析构函数、移动函数

Lambda表达式

利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,具体格式如下:

[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {

// 函数体

}

示例:

int nRet = [](int x, int y) {return x + y;};//返回x和y的和

lamda表达式说明:

  1. []。没有任何函数对象参数。
  2. [=]。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
  3. [&]。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。
  4. [this]。函数体内可以使用 Lambda 所在类中的成员变量。
  5. [a]。将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。
  6. [&a]。将 a 按引用进行传递。
  7. [a,&b]。将 a 按值传递,b 按引用进行传递。
  8. [=,&a,&b]。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
  9. [&,a,b]。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
    注意:值传递,在lambda函数定义时就确定了,不会随lambda引用改变。

线程库

在 C++11 之前,涉及到多线程问题,都是和平台相关的,比如 Windows 和 Linux 下各有自己的接口,这使得代码的可移植性比较差。C++11提供了线程库Thread,

示例:

void func(int n){return n;}
int n=10;
thread t1(func, n);//创建线程t1执行func函数,n为参数
t1.join();//阻塞线程,等待func线程函数执行完成

thread 类常用函数:

?1、 thread():构造一个线程对象,没有关联任何线程函数,即没有启动任何线程。

2、 thread(fn, args1, args2, ...):构造一个线程对象,关联线程函数 fn ,args1,args2,… 为线程函数的参数。

?3、get_id() :获取线程 id 。

?4、 joinable():判断线程对象是否可以被 join 。

?5、join():阻塞等待线程对象终止。

?6、detach():分离线程对象。


原子操作

所谓原子操作,其意义就是“原子是最小的,不可分割的最小个体”。当多个线程访问同一个全局资源的时候,能够确保所有其它的线程都不在同一时间访问相同的资源。在c++11之前,多线程访问同一变量时,需要加锁控制,例如:

long total = 0;
mutex m;// 对共享资源进行保护的互斥对象
void threadFunc() {
    for(int i=0; i<1000000;++i) {
        m.lock();// 访问之前,锁定互斥对象
        total++; 
        m.unlock();// 访问完成后,释放互斥对象
    }
}

引入原子操作include <atomic>,代码如下:

atomic_long total(0);
void threadFunc() {
    for(int i=0; i<1000000;++i) {
        total++; //无需加锁控制
    }
}

常见的原子类型有:

原子类型名称

对应内置类型

atomic_bool

bool

atomic_char

atomic_char

atomic_char

signed char

atomic_uchar

unsigned char

atomic_short

short

atomic_ushort

unsigned short

atomic_int

int

atomic_uint

unsigned int

atomic_long

long

atomic_ulong

unsigned long

atomic_llong

long long

atomic_ullong

unsigned long long

atomic_ullong

unsigned long long

atomic_char16_t

char16_t

atomic_char32_t

char32_t

atomic_wchar_t

wchar_t

统一的列表初始化

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

示例:

struct Point {
	int x;
	int y;
};

int a{ 2 }; //支持使用{}的统一初始化了
int array1[]{ 1, 2, 3, 4, 5 };
int* pa = new int[4]{ 0 };
Point p{ 1, 2 };


右值引用和移动构造

顾名思义 ,对左值的引用就是左值引用, 对于右值的引用就是右值引用

定义左值和右值的区别, 可否进行取地址, 可以取地址的就是左值, 不可以取地址的就是右值

示例:

	int a = 10;
	int& b = a;		//此处是左值引用
	int&& c = 2;	//此处是右值引用
  int&& h = std::move(a);	//std::move 作用 将左值引用转换为右值引用

移动构造

移动构造是C++11标准中提供的一种新的构造方法,移动构造接管源对象,既不会产生额外的拷贝开销,也不会给新对象分配内存空间。提高程序的执行效率,节省内存消耗。移动构造的本质是一种资源窃取, 资源转移。

示例:

	class demo{
public:
    demo():num(new int(0)){
        cout<<"construct!"<<endl;
    }
   //拷贝构造
    demo(const demo &d):num(new int(*d.num)){
        cout<<"copy construct!"<<endl;
    }
    //添加移动构造函数
    demo(demo &&d):num(d.num){
        d.num = NULL;
        cout<<"move construct!"<<endl;
    }
    ~demo(){
        cout<<"class destruct!"<<endl;
    }
private:
    int *num;
};

nullptr指针

在C语言中,NULL被定义为(void*)0,而在C++语言中,NULL则被定义为整数0。编译器一般对其实际定义如下:

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

在C++中指针必须有明确的类型定义。但是将NULL定义为0带来的另一个问题是无法与整数的0区分。因为C++中允许有函数重载,所以引入nullptr,nullptr可以明确区分整型和指针类型,能够根据环境自动转换成相应的指针类型,但不会被转换为任何整型,所以不会造成参数传递错误。

移动构造

类型推导 auto 和 decltype

auto 关键字的作用在编译阶段对于=右边的对象进行自动的类型推导

例如:

    auto index = 10;//index为整型
    auto str = "abc";//str为字符串类型
   vector<int>vTest;
   auto iter=vTest.begin();//iter为迭代器类型

decltype的出现是为了补齐auto 不支持对于表达式的类型推导的缺陷的, 经常适用于后置返回类型的推导,如下:

template<class T, class U> 
auto Add(T&& t, U&& u) 
		->decltype(std::forward<T>(t) +std::forward<T>(u)) {
	return std::forward<T>(t) +std::forward<T>(u);
}
 
int main() {
	auto func = [](int a, double b)->decltype(a + b){ return a + b; };
	cout << Add(2, 5);
	while (1);
	return 0;
}
点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4