智能指针

C++中的智能指针(Smart Pointers)是一种用于自动管理动态内存的工具,它们通过RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则来管理对象的生命周期,从而减少内存泄漏和其他内存管理问题。智能指针在C++11中引入,并在标准库中提供了几种常用的类型:std::unique_ptrstd::shared_ptrstd::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 = ptr1; // 错误:不允许复制
std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权
std::cout << "ptr2: " << *ptr2 << std::endl;
// std::cout << "ptr1: " << *ptr1 << std::endl; // 错误:ptr1不再拥有对象
}

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; // 输出: 2
}

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()) { // 安全获取 shared_ptr
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::mutexstd::recursive_mutexstd::timed_mutexstd::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 <= 0return;
    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; }); // 等待条件成立,返回false前一直阻塞
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::futurestd::promise

std::futurestd::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::promisestd::future 之间的关系就像生产者和消费者:std::promise 设置值,std::future 获取值。

  • 异步任务结果传递:在多线程编程中,一个线程可以执行异步任务,并通过 std::promise 将结果传递给另一个线程。
  • 线程间同步:可以用于在线程之间同步状态或信号。

以下是一个简单的例子,展示如何使用 std::promisestd::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; // 创建 promise
std::future<int> fut = prom.get_future(); // 获取关联的 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> // std::forward

// 一个简单的函数模板,接收万能引用参数
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; // y 的类型是 int

auto func = [](int a, int b) { return a + b; };
decltype(func(1, 2)) result = 3; // result 的类型是 int
  • auto的区别auto用于类型推导,而decltype用于类型查询。auto会进行类型推导和调整(如移除引用和const),而decltype会精确地返回表达式的类型。

  • 复杂表达式decltype也可以用于更复杂的表达式,例如成员访问或函数调用。

1
2
3
4
5
6
struct Point {
int x, y;
};

Point p;
decltype(p.x) x = 0; // x 的类型是 int

lambda表达式

Lambda表达式的基本语法

一个lambda表达式的基本语法如下:

1
2
3
[capture](parameters) -> return_type {
// function body
}
  • 捕获列表 [capture]:用于捕获lambda所在作用域中的变量。捕获可以是按值捕获(=)或按引用捕获(&)。你也可以指定具体的变量如何捕获。

  • 参数列表 (parameters):和普通函数的参数列表类似,指定lambda的参数。

  • 返回类型 -> return_type:可选,用于指定lambda的返回类型。如果省略,编译器会尝试自动推导。

  • 函数体 {}:包含lambda的具体实现代码。

示例

以下是一些使用lambda表达式的示例:

  1. 基本示例
1
2
3
4
5
auto add = [](int a, int b) -> int {
return a + b;
};

int result = add(3, 4); // result = 7
  1. 捕获变量
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(); // result = 30
y = 30;
result = addXY(); // result = 40, 因为 y 是按引用捕获
  1. 在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};

// 使用lambda表达式在STL算法中
std::for_each(numbers.begin(), numbers.end(), [](int n) {
std::cout << n << " ";
});

return 0;
}
  1. 省略返回类型

    如果lambda的函数体只有一个return语句,返回类型可以省略,编译器会自动推导:

1
2
3
auto multiply = [](int a, int b) {
return a * b; // 返回类型自动推导为 int
};

捕获模式

  • 按值捕获[=] 捕获外部作用域中所有变量的副本。
  • 按引用捕获[&] 捕获外部作用域中所有变量的引用。
  • 混合捕获[=, &var][&, var] 可以混合使用按值和按引用捕获特定变量。

Lambda表达式的特性

  • Lambda表达式是一个轻量级的语法糖,用于定义内联函数对象。
  • 它们可以在需要函数对象的任何地方使用,例如回调、STL算法等。
  • 捕获列表使得lambda能够访问和操作其定义所在作用域的变量。
  • Lambda表达式的类型是匿名的,但可以通过autostd::function进行存储。