C++11引入了一个新的内存模型,即C++11内存模型(C++11 memory model)。它定义了多线程并发环境下对共享数据的访问和修改行为,以及对原子操作和同步操作的语义。在这些之上,C++11还提供了对原子类型和无锁编程的支持,并且与之集成。
C++11内存模型确保了一些基本的原则:
C++ 对线程和锁级别编程的支持是 POSIX 和 Windows 所提供的线程和锁的类型安全变体 • thread——系统的执行线程,支持 join() 和 detach() • mutex——系统的互斥锁,支持 lock()、unlock() 和保证 unlock() 的 RAII 方式 • condition_variable——系统中线程间进行事件通信的条件变量 • thread_local——线程本地存储
线程和锁模型需要使用某种形式的同步来避免竞争条件。C++11 为此提供了标准 的 mutex(互斥锁)
C++11 提供了:
• future——一个句柄,通过它你可以从一个共享的单对象缓冲区中 get() 一个值,可能需要等待某个 promise 将该值放入缓冲区。
• promise——一个句柄,通过它你可以将一个值 put() 到一个共享的单对象缓冲区,可能会唤醒某个等待 future 的 thread。
• packaged_task——一个类,它使得设置一个函数在线程上异步执行变得容易,由 future 来接受 promise 返回的结果。
• async()——一个函数,可以启动一个任务并在另一个 thread 上执行。
C++11中用auto关键字来支持自动类型推导。用decltype推导表达式类型。头文件:#include<typeinfo>
auto:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型。(前提是定义一个变量时对其进行初始化)
auto a = 10; // 10是int型,可以自动推导出a是intdecltype:用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。(decltypde是不需要推导变量初始化的,根据的是表达式对变量的类型就可以推导。)
auto varname = value;
decltype(exp) varname = value;
decltype(10.8) x; //x 被推导成了 double两者区别:
1,auto用于变量的类型推导,根据初始化表达式的类型来推导变量的类型,常用于简化代码和处理复杂类型。而decltype则用于获取表达式的类型,保留修饰符,并且可以进行表达式求值。
2,auto在初始化时进行类型推导,而decltype直接查询表达式的类型,可以用于任何表达式,包括没有初始化的变量。
3,auto在编译期间确定类型,并且无法更改。而decltype在运行时才确定表达式的类型。
4,auto适用于简单的类型推导,而decltype适用于复杂的类型推导和获取表达式的结果类型。
在C++11中,引入了范围for循环(Range-based for loop),它提供了一种简洁而直观的方式来遍历容器、数组、字符串和其他可迭代对象。
for (auto element : container) {
// 操作每个元素
}其中,element 是一个变量,用于存储容器中的每个元素的值。container 是一个可迭代对象,例如数组、标准库容器或自定义容器。
范围for循环的工作原理是,它会自动遍历容器中的每个元素,并将当前元素的值赋给 element 变量,然后执行循环体中的代码块。循环体会针对容器中的每个元素执行一次。
//实例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto number : numbers) {
std::cout << number << " ";
}
return 0;
}在C++11中,引入了新的智能指针类,用于更安全和方便地管理动态分配的资源,避免内存泄漏和悬空指针等问题。以下是C++11中的三种主要智能指针:
【1】std::unique_ptr:
1,std::unique_ptr 是一种独占式智能指针,用于管理唯一的对象,确保只有一个指针可以访问该对象。
2,使用 std::unique_ptr 可以自动释放动态分配的内存,当指针超出作用域或被重置时,它会自动删除所管理的对象。
3,通过 std::make_unique 函数可以创建 std::unique_ptr 对象,如:std::unique_ptr<int> ptr = std::make_unique<int>(42);
【2】std: :shared_ptr:
1,std::shared_ptr 是一种共享式智能指针,多个指针可以同时共享对同一对象的拥有权。
2,std::shared_ptr 使用引用计数技术追踪所管理对象的引用数量,当引用计数变为零时,自动销毁所管理的对象。
3,通过 std::make_shared 函数可以创建 std::shared_ptr 对象,如:std::shared_ptr<int> ptr = std::make_shared<int>(42);
【3】std::weak_ptr:
1,std::weak_ptr 是一种弱引用智能指针,它可以解决 std::shared_ptr 的循环引用问题。
2,std::weak_ptr 指向 std::shared_ptr 管理的对象,但不会增加引用计数。因此,当所有 std::shared_ptr 对象超出作用域后,即使还有 std::weak_ptr 对象存在,所管理的对象也会被销毁。
3,通过 std::shared_ptr 的 std::weak_ptr 构造函数可以创建 std::weak_ptr 对象,如:std::weak_ptr<int> weakPtr = sharedPtr;
注意:“它们的确智能,但它们仍然是指针。”除非我们确实需要指针,否则,简单地使用局部变量会更好
C++11 引入了统一初始化(Uniform Initialization)语法,这使得对象的初始化更加一致和简洁。使用统一初始化,可以通过多种方式来初始化对象,无论是基本类型、类类型还是数组都可以使用相同的语法。
在 C++11 中,统一初始化有以下几种形式:
int x = 42; // 直接初始化一个整数
std::string s("Hello"); // 直接初始化一个字符串对象
int y = x; // 使用拷贝初始化将 x 的值赋给 y
std::vector<int> v = {1, 2, 3}; // 使用拷贝初始化进行向量初始化
int z{123}; // 使用列表初始化一个整数
std::vector<int> nums{1, 2, 3}; // 使用列表初始化初始化一个向量
std::pair<int, double> p{42, 3.14}; // 使用列表初始化初始化一个键值对nullptr是c++11用来表示空指针新引入的常量值,在c++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是个int型的0,其实不是个指针。举例:
void func(void *ptr) {
cout << "func ptr" << endl;
}
void func(int i) {
cout << "func i" << endl;
}
int main() {
func(NULL); // 编译失败,会产生二义性
func(nullptr); // 输出func ptr
return 0;
}constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。
常量表达式(const experssion)是指:
(1)值不会改变
(2)在编译过程就能得到计算结果的表达式。
constexpr和const的区别:
两者都代表可读,
const只表示read only的语义,只保证了运行时不可以被修改,但它修饰的仍然有可能是个动态变量,
而constexpr修饰的才是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变,constexpr可以用于修饰函数,这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理。
#include<iostream>
using namespace std;
constexpr int func(int i) {
return i + 1;
}
int main() {
int i = 2;
func(i);// 普通函数
func(2);// 编译期间就会被计算出来
}explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换,根据代码看explicit的作用:
struct A {
explicit A(int value) {
cout << "value" << endl;
}
};
int main() {
A a = 1; // error,不可以隐式转换
A aa(2); // ok
return 0;
}c++11关于继承新增了两个关键字,final用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载,override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误。
struct Base {
virtual void func() {
cout << "base" << endl;
}
};
struct Derived : public Base{
void func() override { // 确保func被重写
cout << "derived" << endl;
}
void fu() override { // error,基类没有fu(),不可以被重写
}
};C++ 中的右值引用(Rvalue reference)是一种引用类型,它用于绑定到临时对象或将要被移动的对象(右值)。通过右值引用,我们可以对右值进行有效的操作,如移动语义和完美转发。
右值引用的语法是在类型后面加上 &&,例如 int&& 表示一个右值引用到 int 类型的对象。右值引用只能绑定到右值,不能绑定到左值。
右值引用主要有两个重要的应用场景:移动语义和完美转发。
【1】移动语义: 右值引用使得我们可以实现高效的资源管理,尤其是在处理动态分配的内存或大型对象时。通过移动语义,我们可以将资源从一个对象转移到另一个对象,避免了不必要的拷贝开销。 通过定义移动构造函数和移动赋值运算符,并使用右值引用参数,可以实现对资源的高效转移。移动构造函数用于在构造对象时从临时或将要被销毁的对象中“窃取”资源,移动赋值运算符用于在对象已存在时将资源从右值赋值给对象。这样,在资源转移完成后,原始对象就不再拥有该资源,而新对象拥有该资源,避免了多余的内存分配和拷贝操作。
【2】完美转发: 完美转发是指在函数模板中保持参数的值类别(左值或右值)并将其转发到其他函数,以实现泛型编程中的通用参数传递。通过使用右值引用和模板参数推导,可以实现参数类型的自动推导和类型保持。
在函数模板中使用右值引用参数可以接收右值和左值,并保持参数的原始类型。结合 std::forward 可以实现完美转发,将参数以原始类型转发到其他函数。这样,在调用模板函数时,参数的值类别被保留,从而选择正确的函数进行处理。
右值引用在 C++11 中引入,它的出现在很大程度上优化了资源管理和提升了代码的性能。它为移动语义和完美转发提供了重要的基础,并在现代 C++ 开发中广泛应用。
C++11 引入了移动语义(Move Semantics)的概念,旨在提高对象的性能和效率。移动语义通过转移资源所有权,避免不必要的拷贝操作,从而更高效地管理对象。
在传统的拷贝语义中,当我们将一个对象赋值给另一个对象或者作为函数参数传递时,会进行对象的拷贝操作,这包括复制所有成员变量的值、分配内存等。在某些情况下,这种拷贝操作是非常昂贵的,特别是对于大型对象或者资源密集型的操作。
移动语义通过引入右值引用(Rvalue Reference)来解决这个问题。右值引用使用 && 语法进行声明,表示一个临时对象或者即将销毁的对象。在移动语义中,我们可以将资源的所有权从一个对象转移到另一个对象,而不需要进行昂贵的拷贝操作。
在使用移动语义时,可以借助 std::move 函数将左值转换为右值引用,以便进行移动操作。下面是一个简单的示例:
#include <iostream>
#include <vector>
class MyObject {
public:
MyObject() {
std::cout << "Default constructor" << std::endl;
// 假设需要分配大量内存或进行其他资源密集型操作
}
MyObject(const MyObject& other) {
std::cout << "Copy constructor" << std::endl;
// 实现对象的拷贝操作
}
MyObject(MyObject&& other) {
std::cout << "Move constructor" << std::endl;
// 实现对象的移动操作
}
};
int main() {
MyObject obj1; // 调用默认构造函数
MyObject obj2(obj1); // 调用拷贝构造函数,拷贝 obj1 的值到 obj2
MyObject obj3(std::move(obj1)); // 调用移动构造函数,将 obj1 的值转移到 obj3
return 0;
}通过移动语义,我们可以避免不必要的拷贝操作,提高代码的性能和效率。特别是对于容器类(如 std::vector、std::string)或动态分配的资源,利用移动语义可以显著降低内存分配和复制的开销。
需要注意的是,移动构造函数的实现通常是将源对象指针设置为 nullptr,以确保在析构时不会释放已经被转移的资源。此外,移动构造函数和拷贝构造函数应该遵循特定的语义规范,以确保正确、可预期的行为。
完美转发(perfect forwarding)是 C++ 中用于保持传递参数类型和转发函数调用的机制。它通常与模板和右值引用一起使用,以实现泛型编程中的参数传递。
在传统的函数调用中,如果我们想要将一个函数的参数传递给另一个函数,通常可以直接通过值传递或引用传递来实现。但是问题在于,当我们希望将参数以原始类型(值类型或引用类型)传递给另一个函数时,需要显式指定参数的类型,而无法自动推导。
完美转发解决了这个问题,它允许我们在一层函数中接收参数,并将其转发到另一层函数,同时保持参数的原始类型。这样就可以实现泛型编程中的通用参数传递,不再需要手动指定参数类型。
完美转发的核心是使用了两种类型:通用引用和 std::forward。
auto&& 或模板参数推导结合引用折叠规则的引用类型。通用引用可以绑定到左值或右值,并保持参数的原始类型。std::forward 是一个条件转发的工具函数,用于根据参数的原始类型,选择性地将参数转发为左值引用或右值引用。它的使用场景通常是在模板函数或模板类中,用于将参数转发到另一个函数。下面是一个简单的示例,演示了完美转发的使用:
#include <iostream>
#include <utility>
void processValue(int& value)
{
std::cout << "Lvalue: " << value << std::endl;
value = 42;
}
void processValue(int&& value) {
std::cout << "Rvalue: " << value << std::endl;
}
//forwardValue 是一个模板函数,它使用了通用引用来接收参数,并使用 std::forward 将参数转发给 processValue 函数。通过 std::forward,参数的原始类型可以被保持,并且传递给正确的版本进行处理。
template<typename T>
void forwardValue(T&& value)
{
processValue(std::forward<T>(value));
}
//在 main 函数中,我们先传递了一个左值 x 给 forwardValue 函数,然后传递了一个右值 20。通过完美转发,参数的类型被正确地保持,并且分别调用了 processValue 的左值版本和右值版本。
int main() {
int x = 10;
forwardValue(x); // 传递左值
std::cout << "After forwarding, x = " << x << std::endl;
forwardValue(20); // 传递右值
return 0;
}
//在上述示例中,我们定义了两个函数:processValue 和 forwardValue。processValue 函数重载了一个接收左值引用和右值引用参数的版本,分别对左值和右值做出不同的处理。需要注意的是,在使用完美转发时,通常需要使用模板函数,并搭配通用引用的语法。这样可以保持参数的原始类型,并进行类型推导,从而实现泛型编程中的参数转发。
总结来说,完美转发是一种保持参数类型并转发函数调用的机制。它利用通用引用和 std::forward 实现了参数的泛型传递,避免了手动指定参数类型的问题,增强了代码的重用性和灵活性。