智能指针
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
进行存储。