智能指针
C++中的智能指针(Smart Pointers)是一种用于自动管理动态内存的工具,它们通过RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则来管理对象的生命周期,从而减少内存泄漏和其他内存管理问题。智能指针在C++11中引入,并在标准库中提供了几种常用的类型:std::unique_ptr、std::shared_ptr和std::weak_ptr。下面是对这些智能指针的详细介绍:
1. std::unique_ptr
std::unique_ptr 是一种独占所有权的智能指针,这意味着在任意时刻,只有一个 std::unique_ptr 拥有某个动态分配的对象。当 std::unique_ptr 被销毁时,它所管理的对象也会被自动销毁。
特点
独占所有权 :不允许复制,只能通过移动语义转移所有权。
轻量 :由于不需要引用计数器,开销较小。
安全性 :自动管理资源,防止内存泄漏。
示例
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> #include <memory> void uniquePtrDemo () { std::unique_ptr<int > ptr1 (new int (10 )) ; std::cout << "ptr1: " << *ptr1 << std::endl; std::unique_ptr<int > ptr2 = std::move (ptr1); std::cout << "ptr2: " << *ptr2 << std::endl; }
2. std::shared_ptr
std::shared_ptr 是一种共享所有权的智能指针,多个 std::shared_ptr 可以指向同一个对象。当最后一个 std::shared_ptr 被销毁时,所管理的对象才会被释放。
特点
共享所有权 :允许多个指针共享同一个对象。
引用计数 :内部维护一个引用计数器来跟踪指向对象的指针数量。
线程安全 :引用计数的增减是线程安全的。
示例
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include <memory> void sharedPtrDemo () { std::shared_ptr<int > ptr1 (new int (20 )) ; std::cout << "ptr1: " << *ptr1 << std::endl; std::shared_ptr<int > ptr2 = ptr1; std::cout << "ptr2: " << *ptr2 << std::endl; std::cout << "Use count: " << ptr1. use_count () << std::endl; }
3. std::weak_ptr
std::weak_ptr 是一种不拥有对象所有权的智能指针,主要用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 不影响引用计数,因此它不能直接访问所管理的对象,必须先转换为 std::shared_ptr。
oc里有weak strong dance 来破解循环引用的问题
特点
不拥有所有权 :不增加引用计数。
防止循环引用 :用于打破 std::shared_ptr 之间的循环引用。
安全访问 :通过 lock 方法安全地获取对象的 std::shared_ptr。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> #include <memory> void weakPtrDemo () { std::shared_ptr<int > sharedPtr1 = std::make_shared <int >(30 ); std::weak_ptr<int > weakPtr = sharedPtr1; std::cout << "Use count before: " << sharedPtr1. use_count () << std::endl; if (auto lockedPtr = weakPtr.lock ()) { std::cout << "Locked value: " << *lockedPtr << std::endl; } std::cout << "Use count after: " << sharedPtr1. use_count () << std::endl; }
C++11 新标准的并发
1. std::thread
std::thread类是C++11中用于创建和管理线程的基本工具。它允许你启动一个新线程来执行某个函数或可调用对象。
示例
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> #include <thread> void threadFunction () { std::cout << "Hello from thread!" << std::endl; } int main () { std::thread t (threadFunction) ; t.join (); return 0 ; }
join() :阻塞当前线程,直到目标线程完成。
detach() :将线程分离,使其在后台运行,主线程不会等待。
2. 互斥锁(Mutex)
互斥锁用于保护共享数据免受多个线程的并发访问。C++11提供了std::mutex、std::recursive_mutex、std::timed_mutex和std::recursive_timed_mutex。
std::recursive_mutex可重入锁.
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <thread> #include <mutex> std::mutex mtx; void printMessage (const std::string& message) { std::lock_guard<std::mutex> lock (mtx) ; std::cout << message << std::endl; } int main () { std::thread t1 (printMessage, "Hello from thread 1" ) ; std::thread t2 (printMessage, "Hello from thread 2" ) ; t1. join (); t2. join (); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <thread> #include <mutex> #include <chrono> std::recursive_timed_mutex recTimedMutex; void timedFunction (int count) { if (count <= 0 ) return ; if (recTimedMutex.try_lock_for (std::chrono::milliseconds (100 ))) { std::cout << "Lock acquired, count: " << count << std::endl; timedFunction (count - 1 ); recTimedMutex.unlock (); std::cout << "Lock released, count: " << count << std::endl; } else { std::cout << "Failed to acquire lock, count: " << count << std::endl; } } int main () { std::thread t1 (timedFunction, 3 ) ; t1. join (); return 0 ; }
std::lock_guard :RAII风格的锁管理,自动获取和释放锁。
std::unique_lock :提供更灵活的锁管理,可以显式锁定和解锁。
3. 条件变量(Condition Variable)
条件变量用于线程间的通知机制,允许一个线程等待某个条件成立,而另一个线程可以通知它条件已经成立。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <thread> #include <mutex> #include <condition_variable> std::mutex mtx; std::condition_variable cv; bool ready = false ;void worker () { std::unique_lock<std::mutex> lock (mtx) ; cv.wait (lock, [] { return ready; }); std::cout << "Worker thread is processing data." << std::endl; } int main () { std::thread t (worker) ; { std::lock_guard<std::mutex> lock (mtx) ; ready = true ; } cv.notify_one (); t.join (); return 0 ; }
4. 原子操作(Atomic Operations)
C++11提供了std::atomic模板类,用于实现原子操作,确保对变量的操作是线程安全的,而不需要显式使用互斥锁。
还有些更高级的操作,比如CAS操作,这里暂时先不做讲解.
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <thread> #include <atomic> std::atomic<int > counter (0 ) ;void increment () { for (int i = 0 ; i < 1000 ; ++i) { ++counter; } } int main () { std::thread t1 (increment) ; std::thread t2 (increment) ; t1. join (); t2. join (); std::cout << "Counter: " << counter << std::endl; return 0 ; }
5. std::future 和 std::promise
std::future和std::promise用于在异步操作中传递结果。
std::promise :用于设置异步操作的结果。
std::future :用于获取异步操作的结果。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <thread> #include <future> int calculate () { std::this_thread::sleep_for (std::chrono::seconds (2 )); return 42 ; } int main () { std::future<int > result = std::async (std::launch::async, calculate); std::cout << "Waiting for result..." << std::endl; std::cout << "Result: " << result.get () << std::endl; return 0 ; }
std::promise 代表一个值的承诺(promise),它可以在某个时刻被设置,并且这个值可以通过关联的 std::future 被获取。std::promise 和 std::future 之间的关系就像生产者和消费者:std::promise 设置值,std::future 获取值。
异步任务结果传递 :在多线程编程中,一个线程可以执行异步任务,并通过 std::promise 将结果传递给另一个线程。
线程间同步 :可以用于在线程之间同步状态或信号。
以下是一个简单的例子,展示如何使用 std::promise 和 std::future:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <thread> #include <future> void calculate (std::promise<int > prom, int a, int b) { std::this_thread::sleep_for (std::chrono::seconds (2 )); int result = a + b; prom.set_value (result); } int main () { std::promise<int > prom; std::future<int > fut = prom.get_future (); std::thread t (calculate, std::move(prom), 10 , 20 ) ; std::cout << "Waiting for result..." << std::endl; int result = fut.get (); std::cout << "Result: " << result << std::endl; t.join (); return 0 ; }
- 一次性设置 :std::promise 的值或异常只能设置一次,多次调用 set_value 或 set_exception 会导致异常。
- 移动语义 :由于 std::promise 不可复制,通常需要通过 std::move 将其传递给其他线程或函数。
- 同步机制 :std::promise 和 std::future 提供了一种简单的同步机制,适用于需要在多个线程之间传递结果或状态的场景。
6. 线程本地存储(Thread Local Storage)
C++11引入了线程本地存储关键字thread_local,用于声明每个线程都有独立实例的变量。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <thread> thread_local int localVar = 0 ;void increment () {++localVar; std::cout << "Local variable: " << localVar << std::endl; } int main () {std::thread t1 (increment) ;std::thread t2 (increment) ;t1. join (); t2. join (); return 0 ;}
万能引用
万能引用能够同时绑定到左值和右值,因此它在泛型编程中非常有用,特别是在实现完美转发(Perfect Forwarding)时。
万能引用(又名未定义引用,英文名universal reference)离不开上面提到的两种语境,这两种语境必须同时存在:
· 必须是函数模板。
· 必须是发生了模板类型推断并且函数模板形参长这样:T&&。
1 2 template <typename T>void func (T&& param) ;
- 语法依赖 :万能引用的识别依赖于模板参数推导。如果没有模板参数推导(例如在非模板函数或显式指定类型时),T&& 表示的是右值引用,而不是万能引用。
- 与右值引用的区别 :虽然语法上相同,万能引用和右值引用的语义是不同的。右值引用只能绑定到右值,而万能引用可以绑定到左值和右值。
- 使用 std::forward :在实现完美转发时,必须使用 std::forward 来保持参数的值类别。std::forward 会根据传递的模板参数类型正确地将参数转发为左值或右值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <utility> template <typename T>void process (T&& arg) { if constexpr (std::is_lvalue_reference_v<T>) { std::cout << "Lvalue reference" << std::endl; } else { std::cout << "Rvalue reference" << std::endl; } std::cout << "Processing: " << arg << std::endl; } template <typename T>void forwardToProcess (T&& arg) { process (std::forward<T>(arg)); } int main () { int x = 10 ; forwardToProcess(x); forwardToProcess(20 ); return 0 ; }
引用折叠
左值引用是一个&表示,右值引用是两个&&表示,所以这两种引用组合一下就会有四种可能的组合:
· 左值-左值& - & 左值引用
· 左值-右值& - && 左值引用
· 右值-左值&&-& 左值引用
· 右值-右值&&-&& 右值引用
constexpr 和 decltype区别
constexpr
constexpr是C++11引入的关键字,用于指示一个函数或变量可以在编译时求值。这对于提高程序效率和安全性非常有用,因为编译器可以在编译时进行更多优化。
constexpr函数 :一个函数被声明为constexpr,意味着它可以用于常量表达式中。这样的函数必须满足以下条件:
函数体中只有一条return语句。
函数参数和返回类型必须是字面量类型。
在C++14及以后,constexpr函数可以有更复杂的实现,包括局部变量和循环。
1 2 3 4 5 6 7 8 constexpr int square (int x) { return x * x; } int main () { constexpr int result = square (5 ); return 0 ; }
constexpr变量 :声明为constexpr的变量必须在声明时初始化,并且初始化表达式必须是一个常量表达式。
1 constexpr int max_size = 100 ;
decltype
decltype是C++11引入的关键字,用于查询表达式的类型。它在模板编程和泛型代码中非常有用,因为它允许在不知道表达式具体类型的情况下获取该类型。
基本用法 :decltype可以用于获取变量、表达式或函数调用的类型。
1 2 3 4 5 int x = 10 ;decltype (x) y = 20 ; auto func = [](int a, int b) { return a + b; };decltype (func (1 , 2 )) result = 3 ;
1 2 3 4 5 6 struct Point { int x, y; }; Point p; decltype (p.x) x = 0 ;
lambda表达式
Lambda表达式的基本语法
一个lambda表达式的基本语法如下:
1 2 3 [capture](parameters) -> return_type { }
捕获列表 [capture] :用于捕获lambda所在作用域中的变量。捕获可以是按值捕获(=)或按引用捕获(&)。你也可以指定具体的变量如何捕获。
参数列表 (parameters) :和普通函数的参数列表类似,指定lambda的参数。
返回类型 -> return_type :可选,用于指定lambda的返回类型。如果省略,编译器会尝试自动推导。
函数体 {} :包含lambda的具体实现代码。
示例
以下是一些使用lambda表达式的示例:
基本示例 :
1 2 3 4 5 auto add = [](int a, int b) -> int { return a + b; }; int result = add (3 , 4 );
捕获变量 :
1 2 3 4 5 6 7 8 9 10 int x = 10 ;int y = 20 ;auto addXY = [x, &y]() { return x + y; }; int result = addXY (); y = 30 ; result = addXY ();
在STL算法中使用 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <vector> #include <algorithm> #include <iostream> int main () { std::vector<int > numbers = {1 , 2 , 3 , 4 , 5 }; std::for_each(numbers.begin (), numbers.end (), [](int n) { std::cout << n << " " ; }); return 0 ; }
省略返回类型 :
如果lambda的函数体只有一个return语句,返回类型可以省略,编译器会自动推导:
1 2 3 auto multiply = [](int a, int b) { return a * b; };
捕获模式
按值捕获 :[=] 捕获外部作用域中所有变量的副本。
按引用捕获 :[&] 捕获外部作用域中所有变量的引用。
混合捕获 :[=, &var] 或 [&, var] 可以混合使用按值和按引用捕获特定变量。
Lambda表达式的特性
Lambda表达式是一个轻量级的语法糖,用于定义内联函数对象。
它们可以在需要函数对象的任何地方使用,例如回调、STL算法等。
捕获列表使得lambda能够访问和操作其定义所在作用域的变量。
Lambda表达式的类型是匿名的,但可以通过auto或std::function进行存储。