很好的面试资料: 整理了一年的Linux C++武林秘籍,你早晚用得到(C++进阶必看) - 知乎

转载并简单整理自知乎 程序喵大人

1.左值引用与完美转发等

基本概念:

左值、右值 :放在等号左边,可以取地址并且有名字 的就叫左值,反之叫右值

1
2
int a = b + c ;
int a = 4;

纯右值:用于初始化一个对象。 运算表达式产生的临时变量、不和对象关联的原始字面量、返回非引用的函数调用、lambda表达式、this指针

将亡值:代表一个“即将结束生命周期”的对象,

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
int main() {
std::string str = "Hello World"; // str 是一个左值

// std::move(str) 将左值 str 转换为一个将亡值
// 它在告诉编译器:“str 即将消亡,请移动它而不是拷贝它”
std::string new_str = std::move(str);

// 此时,str 的资源(动态分配的字符数组)被“移动”到了 new_str 中
// str 本身仍然存在(是一个合法的对象),但处于“有效但未指定状态”
// 通常是一个空字符串,但你不能依赖这一点,只能对它进行重新赋值或销毁
std::cout << "str after move: \"" << str << "\"" << std::endl; // 可能是 ""
std::cout << "new_str: \"" << new_str << "\"" << std::endl; // "Hello World"

return 0;
}

//std::move(str) 就是一个将亡值。它不是一个新创建的值(不是纯右值),而是即将失效的现有对象 str 的另一种表现形式

//主要产生方法:
//1. std::move(a);
A a;
auto c = std::move(a); // std::move(a) 是一个将亡值

//2.static_cast<A&&>(a)
A a;
auto d = static_cast<A&&>(a); // static_cast<A&&>(a) 是一个将亡值

左值、右值引用:

右值引用。是一种特殊的引用类型,主要设计用来绑定到右值(特别是将亡值),并表明所引用的对象资源可以被安全地”移动”或”窃取”。

1
2
3
4
5
6
7
8
9
int a=5;
int& b=a;//b是左值引用
b=4;
int& c = 10;//error,10无法取地址,无法进行引用
const int &d = 10;//ok,因为是常引用,引用常量数字,这个常量数字会存储在内存中,可以取地址

int a=4;
int&& b=a;//error,a是左值
int&& c=std::move(a);//ok

移动语义

浅拷贝容易导致资源重复释放等问题,深拷贝会导致额外的开销。

因此引入了移动语义,也就是实现了所有权管理,偷掉一些将亡值的资源

C++中所有的STL都实现了移动语义,方便使用。

实现移动语义的关键组件

1.右值引用(T&&)

2.移动构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyString {
private:
char* m_data;
size_t m_size;

public:
// 移动构造函数
MyString(MyString&& other) noexcept
: m_data(other.m_data), m_size(other.m_size) // "偷"资源
{
// 将源对象置于有效但空的状态
other.m_data = nullptr;
other.m_size = 0;
}
};

3.移动赋值运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyString {
public:
// 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] m_data; // 释放当前资源

// "偷"资源
m_data = other.m_data;
m_size = other.m_size;

// 置空源对象
other.m_data = nullptr;
other.m_size = 0;
}
return *this;
}
};

//赋值和构造的区别:赋值是替换,构造是创建加赋值

编译器优化:RVO/NRVO

现代编译器会进行返回值优化(RVO)命名返回值优化(NRVO),有时甚至比移动语义更高效

返回值优化的时机:

  • return的值类型与函数的返回值类型相同
  • return的是一个局部对象
1
2
3
4
5
6
7
8
9

MyString create_string() {
return MyString("Hello"); // RVO:直接在目标位置构造,无拷贝无移动
}

MyString create_string_nrvo() {
MyString result("Hello");
return result; // NRVO:直接在目标位置构造result
}

完美转发

完美转发指的是在函数模板中,将参数以原始的值类别(左值或右值)和const/volatile限定符完全不变地转发给另一个函数。

简单来说:保持参数的所有特性不变地传递下去

为什么需要完美转发?

在没有完美转发之前,泛型编程中会遇到参数类别丢失的问题:

1
2
3
void wrapper(Foo& arg) { callee(arg); }
void wrapper(const Foo& arg) { callee(arg); }
void wrapper(Foo&& arg) { callee(std::move(arg)); }

使用模板可以简化

1
2
3
4
template<typename T>
void wrapper(T arg) {
callee(arg);
}

但问题是:

  • 如果传的是右值,T 会推导成 非引用类型,导致右值变成左值,例如调用wrapper(42);时模板会翻译成warpper(int);会用右值初始化为一个新的局部变量arg,变成了左值。

  • const/volatile 属性也可能丢失。

    万能引用与引用折叠
1
2
template<typename T>
void wrapper(T&& arg);

这里的T&&

  • 当传入右值时:T 推导为 U,于是参数类型为 U&& → 右值引用;
  • 当传入左值时:T 推导为 U&,于是参数类型为 U& && → 引用折叠 → U&

引用折叠规则:

  • & &&
  • & &&&
  • && &&
  • && &&&&

所以 T&& 在模板中既能绑定左值,也能绑定右值。

std::forward

wrapper 中直接写:

1
callee(arg);

会把所有参数当成左值传递,右值的性质丢失。
需要用 std::forward<T>(arg) 来“条件转发”:

  • 如果 T 推导为 U&,则 std::forward<T>(arg) 返回 U&
  • 如果 T 推导为 U,则 std::forward<T>(arg) 返回 U&&

这样右值保持右值,左值保持左值,实现完美转发。

典型写法
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 <utility>

void callee(int& x) {
std::cout << "callee(int&)\n";
}
void callee(const int& x) {
std::cout << "callee(const int&)\n";
}
void callee(int&& x) {
std::cout << "callee(int&&)\n";
}

template<typename T>
void wrapper(T&& arg) {
callee(std::forward<T>(arg)); // 完美转发
}

int main() {
int a = 42;
const int b = 100;
//思考输出?
wrapper(a); // callee(int&)
wrapper(b); // callee(const int&)
wrapper(10); // callee(int&&)
}

典型应用场景

1.壳函数

例如日志、装饰器模式、函数调用计时器

1
2
3
4
5
template<typename F, typename... Args>
void log_and_call(F&& f, Args&&... args) {
std::cout << "Calling function...\n";
std::forward<F>(f)(std::forward<Args>(args)...);
}

2.容器构造器

1
2
3
4
5
6
7
8
9
10
11
//vector::emplace_back, map::emplace
template<typename... Args>
void emplace_back(Args&&... args) {
new (&storage[size++]) T(std::forward<Args>(args)...);
}

// push_back:需要显式移动
names.push_back(std::move(temp)); // 移动语义

// emplace_back:完美转发参数,直接构造
names.emplace_back("David"); // 直接在vector中构造,更高效!

2.智能指针

C++11 标准库在 <memory> 头文件中提供了三种主要智能指针:

  1. std::unique_ptr
  2. std::shared_ptr
  3. std::weak_ptr

unique_ptr

std::unique_ptr是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr的拷贝和赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <memory>

struct Foo {
Foo() { std::cout << "Foo ctor\n"; }
~Foo() { std::cout << "Foo dtor\n"; }
};

int main() {
std::unique_ptr<Foo> p1(new Foo()); // 独占所有权
// std::unique_ptr<Foo> p2 = p1; // 错误,不能拷贝
std::unique_ptr<Foo> p3 = std::move(p1); // 可以转移所有权

if (!p1) std::cout << "p1 is empty\n";
}

其内部就是对指针包了一层、然后禁用其拷贝构造、赋值函数:

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
35
template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
T* ptr; // 原始指针
Deleter del; // 删除器,默认调用 delete
public:
explicit unique_ptr(T* p = nullptr) : ptr(p) {}
~unique_ptr() { if (ptr) del(ptr); }

// 禁止拷贝
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

// 支持移动
unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
reset();
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}

T* get() const { return ptr; }
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }

void reset(T* p = nullptr) {
if (ptr) del(ptr);
ptr = p;
}
};

shared_ptr

shared_ptr 使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1, 每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。

注意点:

  • 可以自定义删除器,在引用计数为0的时候自动调用删除器来释放对象的内存:
std::shared_ptrptr(newint,[](int*p){deletep;});
1
std::shared_ptrptr(newint,[](int*p){deletep;});
  • 不要用一个裸指针初始化多个shared_ptr,会出现double_free导致程序崩溃

  • 通过shared_from_this()返回this指针,不要把this指针作为shared_ptr返回出来,因为this指针本质就 是裸指针,通过this返回可能会导致重复析构,不能把this指针交给智能指针管理。

    1
    2
    3
    4
    5
    6
    classA{
    shared_ptr<A>GetSelf(){
    returnshared_from_this();
    //returnshared_ptr<A>(this);错误,会导致doublefree
    }
    };
  • 尽量使用make_shared,少用new。

  • 不要delete get()返回来的裸指针。

  • 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <iostream>
    #include <memory>

    struct Node {
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
    ~Node() {
    std::cout << "Node destroyed\n";
    }
    };

    int main() {
    auto n1 = std::make_shared<Node>();
    auto n2 = std::make_shared<Node>();

    n1->next = n2;
    n2->prev = n1;

    std::cout << "main end\n";
    }

    main函数解释时,栈上的 n1 被销毁 -> n1对象 计数 -1 = 1 栈上的 n2 被销毁 -> n2对象 计数 -1 = 1

    两者的引用计数都不为0 ,可能导致内存泄漏

实现:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
template<typename T>
class shared_ptr {
private:
T* ptr; // 指向资源
ControlBlock* ctrl; // 控制块(包含引用计数)

struct ControlBlock {
size_t strong_count; // 强引用计数(shared_ptr 数量)
size_t weak_count; // 弱引用计数(weak_ptr 数量)
ControlBlock() : strong_count(1), weak_count(0) {}
};

public:
explicit shared_ptr(T* p = nullptr) {
ptr = p;
if (p) ctrl = new ControlBlock();
else ctrl = nullptr;
}

~shared_ptr() {
release();
}

void release() {
if (ctrl) {
if (--ctrl->strong_count == 0) {
delete ptr; // 最后一个强引用销毁资源
if (ctrl->weak_count == 0) {
delete ctrl; // 控制块也销毁
}
}
}
}

// 拷贝构造:增加 strong_count
shared_ptr(const shared_ptr& other) {
ptr = other.ptr;
ctrl = other.ctrl;
if (ctrl) ctrl->strong_count++;
}

// 移动构造:转移所有权
shared_ptr(shared_ptr&& other) noexcept {
ptr = other.ptr;
ctrl = other.ctrl;
other.ptr = nullptr;
other.ctrl = nullptr;
}

// 计数接口
size_t use_count() const {
return ctrl ? ctrl->strong_count : 0;
}
};

weak_ptr

解决循环引用可能出现内存泄漏的问题

弱引用不参与资源的管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <memory>

struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 改成 weak_ptr
~Node() {
std::cout << "Node destroyed\n";
}
};

int main() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();

n1->next = n2;
n2->prev = n1; // weak_ptr,不增加 strong_count

std::cout << "main end\n";
}

在使用 weak_ptr 访问对象时,必须检查其有效性。

3.函数式编程 std::fuction与lambda

lambda表达式,

提供匿名函数的方式,快速定义和使用函数对象

要注意悬空引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::function<void()> createCallback() {
int local_var = 42;
// 危险!捕获了局部变量 local_var 的引用
return [&]() { std::cout << local_var; };
} // local_var 在这里被销毁

int main() {
auto cb = createCallback();
cb(); // 未定义行为!打印的是已销毁栈上的垃圾值。
}

for(int id = enCoef9_Rr ; id <= enCoef9_Bb ;id++){
connect(m_spinBoxs[id],QOverload<double>::of(&QDoubleSpinBox::valueChanged),this,[&](double value){
OnCoefMatrixSlot(id,value);//id 恒等于 0,未定义行为
});
}

默认情况下,以值方式 [=] 捕获的变量在 Lambda 体中是 const 的。如果你想修改它们,必须在参数列表后加上 mutable 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
int count = 0;
// 错误:无法在非 mutable lambda 中修改按值捕获的变量
// auto f = [count]() { count++; };

// 正确:使用 mutable
auto f = [count]() mutable {
count++;
std::cout << count; // 输出的是副本,外部的 count 不变
};
f(); // 输出 1
std::cout << count; // 输出 0 (外部变量未被修改)
}

std::fuction

通用的函数包装器,为各种可调用实体(普通函数、函数指针、lambda、std::bind 表达式、函数对象等)提供了一个统一的类型。这使得我们可以像使用普通变量一样存储和传递函数,极大地增加了代码的灵活性。

std::bind

它可以用来绑定一个可调用对象的部分参数,重新排列参数顺序,或者设置默认参数,从而生成一个新的可调用对象

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
35
36
37
38
39
40
41
42
#include <iostream>
#include <functional>
using namespace std::placeholders; // 对于 _1, _2, _3...

void print_sum(int a, int b, int c) {
std::cout << a + b + c << std::endl;
}

void print_coordinates(int x, int y, int z) {
std::cout << "(" << x << ", " << y << ", " << z << ")\n";
}

class MyClass {
public:
void member_func(int x, const std::string& msg) {
std::cout << "Member func: " << x << ", " << msg << std::endl;
}
};

int main() {
// 1. 绑定参数:将 print_sum 的第三个参数固定为 10
auto bind_func1 = std::bind(print_sum, _1, _2, 10);
// _1 是第一个参数,_2 是第二个参数
bind_func1(5, 3); // 等价于 print_sum(5, 3, 10); 输出 18

// 2. 重排序参数:改变参数顺序
auto bind_func2 = std::bind(print_coordinates, _3, _1, _2); // 新顺序:第三、第一、第二
bind_func2(10, 20, 30); // 等价于 print_coordinates(30, 10, 20); 输出 (30, 10, 20)

// 3. 绑定成员函数
MyClass obj;
// 绑定成员函数需要传递一个对象实例的指针或引用(这里用 &obj)
// _1 将作为成员函数的第一个参数 (int x)
auto bind_member = std::bind(&MyClass::member_func, &obj, _1, "Hello");
bind_member(42); // 等价于 obj.member_func(42, "Hello");

// 4. 与 std::function 结合使用
std::function<void(int, int)> func = std::bind(print_sum, _1, _2, 100);
func(50, 25); // 等价于 print_sum(50, 25, 100); 输出 175

return 0;
}

4.线程

C++11 是 C++ 标准中第一次正式引入跨平台的线程库支持的版本,它将并发和多线程编程纳入了语言和标准库,使得开发者不再依赖 pthread、Windows API 等平台特定的接口。

c++11新特性之线程相关所有知识点 - 简书

基础实现:std::thread

用于创建和管理线程。

构造时接收一个可调用对象(函数、lambda、函数对象等),作为线程的入口。

提供 join()(阻塞等待子线程结束)和 detach()(分离线程,让其后台运行)两种线程生命周期管理方式。

禁止拷贝(避免二义性),支持移动语义

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>

void worker(int id) {
std::cout << "Thread " << id << " is running\n";
}

int main() {
std::thread t(worker, 1);
t.join(); // 等待线程结束
}

std::mutex

1
2
3
4
5
6
7
8
9
10
11
std::mutex m;
{
std::lock_guard<std::mutex> lock(m);
// 临界区
}

std::unique_lock<std::mutex> lock(m);
lock.unlock(); // 可提前解锁
lock.lock(); // 再次加锁

unique_lock开销比lock_guard更大

std::condition_variable

用于线程之间同步,必须与互斥量一起使用。

  • wait(std::unique_lock<std::mutex>& lock, Predicate pred):阻塞当前线程,直到被唤醒且条件谓词 predtrue。它会自动释放锁,并在被唤醒后重新获取锁。
  • notify_one():唤醒一个等待中的线程(如果有)。
  • notify_all():唤醒所有等待中的线程。

典型的生产者消费者写法:

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
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::queue<int> data_queue;
std::mutex mtx;
std::condition_variable cv;
constexpr int MAX_ITEMS = 10;

void producer() {
for (int i = 0; i < MAX_ITEMS; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "Produced: " << i << std::endl;
} // lock 在这里析构解锁
cv.notify_one(); // 通知一个消费者
}
}

void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
// wait 会释放 lock,并在被唤醒后重新获取 lock
// 如果 lambda 返回 false,继续等待;返回 true,则继续执行
cv.wait(lock, []{ return !data_queue.empty(); });

int value = data_queue.front();
data_queue.pop();
lock.unlock(); // 可以提前手动解锁,减少锁的持有时间

std::cout << "Consumed: " << value << std::endl;
if (value == MAX_ITEMS - 1) break;
}
}

int main() {
std::thread prod(producer);
std::thread cons(consumer);

prod.join();
cons.join();
}

std::futurestd::promise - 异步结果

std::future表示一个未来会产生的值,用get()获取结果,可能会阻塞

std::promise提供结果的生产者,和future成对出现,也就是说一个是生产者,一个是消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>
#include <future>

void worker(std::promise<int> p) {
int result = 42;
p.set_value(result); // 把结果传递出去
}

int main() {
std::promise<int> p;
std::future<int> f = p.get_future();

std::thread t(worker, std::move(p));
std::cout << "Result from worker: " << f.get() << std::endl;

t.join();
}
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
#include <iostream>
#include <thread>
#include <future>

void worker(std::promise<int> p) {
try {
throw std::runtime_error("something went wrong");
} catch (...) {
p.set_exception(std::current_exception()); // 传递异常
}
}

int main() {
std::promise<int> p;
std::future<int> f = p.get_future();

std::thread t(worker, std::move(p));

try {
std::cout << f.get() << std::endl; // 这里会抛出异常
} catch (const std::exception &e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}

t.join();
}

std::packaged_task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <future>
#include <iostream>

int heavy_work(int x) {
// 模拟繁重计算
return x * x;
}

int main() {
// 将函数 heavy_work 包装成一个任务
std::packaged_task<int(int)> task(heavy_work);
// 获取与任务结果关联的 future
std::future<int> fut = task.get_future();

// 在线程中运行任务(任务对象不可复制,必须移动)
std::thread t(std::move(task), 10);
t.detach(); // 可以分离,用 future 来获取结果

std::cout << "Result: " << fut.get() << std::endl; // 输出 100
return 0;
}

std::async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <future>
#include <chrono>

int compute() {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
}

int main() {
// 异步启动 compute 函数
std::future<int> fut = std::async(std::launch::async, compute);

// 在主线程做其他事情...
std::cout << "Doing other work...\n";

// 当需要结果时,get() 会阻塞直到结果就绪
int result = fut.get();
std::cout << "The answer is: " << result << std::endl;

return 0;
}

std::atomic

std::atomic 是一个模板类(如 std::atomic<int>, std::atomic<bool>),它包装了一个类型,并提供了一系列保证原子操作的成员函数。原子操作意味着该操作从任何线程的视角看,都是不可分割的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <atomic>

std::atomic<int> counter(0);

// 线程 1 (Writer)
int new_value = compute_expensive_value();
counter.store(new_value, std::memory_order_release); // 原子写

// 线程 2 (Reader)
int current_value = counter.load(std::memory_order_acquire); // 原子读


//std::atomic允许根据场景选择不同的同步强度,在保证正确性的前提下追求极致性能。
//内存顺序通过 std::memory_order 枚举来指定,作为参数传递给 load, store, fetch_* 等操作。
counter.fetch_add(1, std::memory_order_relaxed);

关于内存序,这是一个比较复杂的问题,X86\Riscv的不同强弱内存序也有不同定义与实现。

//std::atomic_flag 是专为实现自旋锁而设计的最简单的原子布尔类型

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
35
36
37
38
39
40
41
//自旋锁是一种忙等待(Busy-Waiting) 锁。当一个线程尝试获取一个已经被其他线程持有的自旋锁时,它不会立即进入睡眠状态(像互斥锁那样),而是会在一个循环中不断地检查锁是否已经被释放(即“自旋”),直到最终获取到锁为止。

#include <atomic>

class Spinlock {
private:
// ATOMIC_FLAG_INIT 确保标志初始为“清除”(false)状态
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;

public:
void lock() {
// test_and_set() 是原子操作:
// 1. 读取当前值
// 2. 无论当前值是什么,都将其设置为 true
// 3. 返回它读取到的**旧值**
while (lock_flag.test_and_set(std::memory_order_acquire)) {
// 如果旧值是 true,说明锁已被占用,循环继续自旋
// 如果旧值是 false,说明成功获取锁,循环结束

// 可选:在自旋等待时提示CPU降低功耗或切换超线程
// __builtin_ia32_pause(); // GCC/Clang intrinsic for x86
// std::this_thread::yield(); // 如果等待时间可能较长,可主动让出时间片
}
}

void unlock() {
// 将标志清除(设为false),释放锁
lock_flag.clear(std::memory_order_release);
}
};

// 使用示例
Spinlock my_lock;
int shared_data = 0;

void critical_section() {
my_lock.lock(); // 获取锁,若失败则自旋等待
shared_data++; // 安全地修改共享数据
// ...其他操作
my_lock.unlock(); // 释放锁
}

自旋锁与互斥锁的比较

特性 自旋锁 (Spinlock) 互斥锁 (Mutex, e.g., std::mutex)
等待机制 忙等待 (Busy-Waiting)。线程在CPU上循环检查,不放弃CPU时间片。 阻塞等待 (Blocking-Wait)。线程被操作系统挂起,放入等待队列,放弃CPU时间片。
开销 获取/释放锁的开销极小(主要是原子CPU指令)。但等待期间消耗CPU周期 获取/释放锁的开销较大(需要进入操作系统内核进行线程调度)。但等待期间不消耗CPU
适用场景 1. 临界区代码非常短(执行速度快)。 2. 等待时间极短3. 不希望发生线程上下文切换(因其开销可能比短暂等待更大)。 例如: 内核编程、无锁数据结构、性能关键的底层代码。 1. 临界区代码执行时间较长2. 等待时间可能较长或不可预测3. 适用于绝大多数应用程序级别的并发例如: 文件操作、数据库访问、复杂的计算过程。
缺点 浪费CPU资源。如果锁被长时间持有,自旋线程会空转CPU,导致性能下降(“烧CPU”)。 上下文切换开销。线程切换需要保存和恢复寄存器状态,开销较大。

短期持有用自旋,长期持有用互斥

5.类型推导

auto

让编译器在编译期根据初始化表达式自动推导出变量的类型

decltype

查询一个表达式(而非初始化器)的类型。它返回该表达式的精确类型,包括引用和 const 限定符。

cpp

本站由 Zane Jiang 使用 Stellar 1.33.1 主题创建,一款很棒的 Hexo 主题!

总访问 次 || 本页访问
总访客 人 || 本页访客