线程本地存储(Thread Local Storage,TLS)是一种机制,它允许每个线程拥有自己独立的变量实例,这些变量的生命周期与线程相同。也就是说,不同线程对同一个 TLS 变量的访问,实际上是在访问各自独立的副本,彼此之间互不干扰。
__declspec(thread)
(Windows)或 __thread
(GCC、Clang)关键字来声明静态 TLS 变量。例如:// 使用 __thread 声明静态 TLS 变量
__thread int tls_variable = 0;
std::thread_local
关键字来声明动态 TLS 变量。例如:// 使用 std::thread_local 声明动态 TLS 变量
thread_local int dynamic_tls_variable = 0;
无锁队列是一种在多线程环境下不使用锁(如互斥锁)来实现线程安全的队列数据结构。无锁队列通常使用原子操作和内存屏障来保证多线程操作的正确性和一致性。下面将介绍如何使用 C++ 实现一个简单的无锁队列,这里采用单生产者单消费者(SPSC)的无锁队列作为示例。
#include
#include
#include
// 定义一个比较仿函数
class Greater {
public:
bool operator()(int a, int b) const {
return a > b;
}
};
int main() {
std::vector numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
// 使用仿函数作为排序规则
std::sort(numbers.begin(), numbers.end(), Greater());
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
std::sort
函数就可以使用仿函数来定义排序规则。c++中引用有没有深浅拷贝的问题
#include
#include // 用于std::strlen和std::strcpy
class MyString {
private:
char* data; // 指向动态分配的字符数组
public:
// 构造函数
MyString(const char* str = "") {
if (str) {
data = new char[std::strlen(str) + 1]; // 分配内存,+1用于存储'\0'
std::strcpy(data, str); // 复制字符串
} else {
data = new char[1];
*data = '\0'; // 空字符串
}
}
// 拷贝构造函数
MyString(const MyString& other) {
data = new char[std::strlen(other.data) + 1];
std::strcpy(data, other.data);
std::cout << "Copy constructor called" << std::endl;
}
// 移动构造函数
MyString(MyString&& other) noexcept {
data = other.data; // 直接接管other的资源
other.data = nullptr; // 将other的指针置为空,避免析构时重复释放
std::cout << "Move constructor called" << std::endl;
}
// 赋值运算符(拷贝赋值)
MyString& operator=(const MyString& other) {
if (this != &other) { // 防止自赋值
delete[] data; // 释放当前对象的资源
data = new char[std::strlen(other.data) + 1];
std::strcpy(data, other.data);
}
return *this;
}
// 赋值运算符(移动赋值)
MyString& operator=(MyString&& other) noexcept {
if (this != &other) { // 防止自赋值
delete[] data; // 释放当前对象的资源
data = other.data; // 接管other的资源
other.data = nullptr; // 将other的指针置为空
}
return *this;
}
// 析构函数
~MyString() {
delete[] data; // 释放动态分配的内存
}
// 获取字符串内容
const char* c_str() const {
return data;
}
// 打印字符串内容
void print() const {
std::cout << data << std::endl;
}
};
int main() {
MyString str1("Hello, World!");
MyString str2(str1); // 调用拷贝构造函数
MyString str3(std::move(str1)); // 调用移动构造函数
std::cout << "str1: ";
str1.print();
std::cout << "str2: ";
str2.print();
std::cout << "str3: ";
str3.print();
return 0;
}
内存泄露的定义内存泄露(Memory Leak)是指程序在动态分配内存后,由于某种原因未能正确释放,导致这部分内存无法被重新使用。随着时间推移,程序占用的内存不断增加,最终可能导致系统内存耗尽,程序运行缓慢甚至崩溃。2\. 定位内存泄露问题定位内存泄露问题通常需要借助一些工具和方法,以下是一些常见的方法:(1)使用内存分析工具• Valgrind:一款开源的内存调试和性能分析工具,适用于Linux平台。它可以检测内存泄漏、越界访问、非法指针等问题。• 使用方法:在终端运行 valgrind ./your_program ,它会生成详细的报告,指出内存泄露的位置。• AddressSanitizer:Google开源的内存错误检测工具,可以检测内存泄漏、缓冲区溢出等问题。• 使用方法:在编译时添加 -fsanitize=address 选项,运行程序时会自动检测内存问题。• LeakSanitizer:专门用于检测内存泄漏的工具。• mtrace:GNU Glibc自带的内存问题检测工具,通过记录 malloc 和 free 的调用来检测内存泄漏。• 使用方法:在代码中调用 mtrace() 和 muntrace() ,并设置环境变量 MALLOC_TRACE ,运行程序后会生成日志文件,通过分析日志可以定位内存泄漏问题。(2)代码审查定期审查代码,查找未释放的内存分配。重点关注以下几点:• 确保每个 malloc 、 calloc 、 realloc 等动态分配内存的函数都有对应的 free 调用。• 检查指针重新赋值、错误的内存释放以及返回值的不正确处理等情况。(3)日志和调试在程序中添加日志记录内存分配和释放的操作。通过分析日志,可以确定哪些内存分配没有被释放。此外,使用调试器(如GDB)也可以追踪内存泄漏。(4)自定义内存分配函数创建自定义的内存分配和释放函数,记录每次分配和释放的内存信息。通过这种方式可以更直观地检测内存泄漏。3\. 处理内存泄露问题一旦发现内存泄露问题,需要采取以下措施进行处理:(1)手动释放内存在程序中进行内存分配时,确保及时释放不再需要的内存。如果忘记释放内存,就会导致内存泄漏。(2)使用智能指针在C++中,使用智能指针(如 std::unique_ptr 、 std::shared_ptr )可以自动管理内存,避免手动释放内存的繁琐操作,从而减少内存泄漏的风险。(3)修复代码中的错误根据内存分析工具的报告或日志信息,修复代码中的错误。例如:• 确保在指针重新赋值前释放原有内存。• 修复错误的内存释放逻辑。• 正确处理函数返回值,避免内存泄漏。(4)定期进行垃圾回收对于一些支持垃圾回收的语言(如Java、Python),可以定期进行垃圾回收,以释放不再使用的内存。4\. 预防内存泄露预防内存泄露比修复内存泄露更为重要,以下是一些预防内存泄露的方法:• 明确内存所有权:每次分配内存时,明确哪个部分负责释放该内存。• 使用RAII(资源获取即初始化):在C++中,通过RAII机制确保资源在对象生命周期结束时自动释放。• 避免使用裸指针:尽量使用智能指针或容器来管理动态内存。• 编写单元测试:通过单元测试检查内存分配和释放的正确性。
vector
、deque
)vector
:当向vector
中插入元素时,如果当前容量不足,vector
会重新分配一块更大的内存,并将原有元素复制到新内存中,然后释放旧内存。这会导致所有指向原vector
的迭代器、指针和引用都失效。#include
#include
int main() {
std::vector vec = {1, 2, 3};
auto it = vec.begin();
vec.insert(vec.begin(), 0); // 插入元素,可能导致迭代器失效
// *it = 10; // 错误,此时迭代器it可能已失效
return 0;
}
deque
:在deque
的中间插入元素会使所有迭代器、指针和引用失效;在deque
的两端插入元素,只有指向插入点的迭代器会失效。std::deque 内部实现是一个块序列,每个块存储固定数量的元素。当删除中间元素时,为了保持块的连续性和完整性,可能会导致以下操作:
- 元素移动:删除中间元素后,后面的元素需要向前移动以填补空缺。
- 块调整:可能需要重新分配块,或者调整块的边界。
最终导致迭代器失效:由于元素的物理位置发生了变化,所有迭代器的指向都会变得不确定。
vector
:删除vector
中的元素时,被删除元素之后的所有迭代器、指针和引用都会失效。#include
#include
int main() {
std::vector vec = {1, 2, 3};
auto it = vec.begin() + 1;
vec.erase(vec.begin()); // 删除元素,后续迭代器失效
// *it = 10; // 错误,此时迭代器it已失效
return 0;
}
deque
:删除deque
中的元素时,除了被删除元素的迭代器失效外,若删除的不是两端元素,所有迭代器、指针和引用都会失效。set
、map
)对于set
和map
等关联式容器,插入元素不会使任何迭代器、指针和引用失效。因为关联式容器使用红黑树等数据结构实现,插入操作只是在树中添加新节点,不会影响原有节点的位置。
#include
#include
int main() {
std::set mySet = {1, 2, 3};
auto it = mySet.begin();
mySet.insert(4); // 插入元素,迭代器不会失效
std::cout << *it << std::endl; // 可以正常使用迭代器
return 0;
}
删除关联式容器中的元素时,被删除元素的迭代器会失效,但其他迭代器不受影响。
#include
#include
int main() {
std::set mySet = {1, 2, 3};
auto it = mySet.begin();
auto nextIt = std::next(it);
mySet.erase(it); // 删除元素,当前迭代器失效
// *it = 10; // 错误,此时迭代器it已失效
std::cout << *nextIt << std::endl; // 可以正常使用其他迭代器
return 0;
}
dynamic_cast
dynamic_cast
主要用于在继承体系中进行安全的向下转型(从基类指针或引用转换为派生类指针或引用),并且会在运行时检查转型的有效性。如果转型失败,对于指针类型会返回 nullptr
,对于引用类型会抛出 std::bad_cast
异常。
常用于多态环境下,当你持有一个基类指针或引用,但需要访问派生类特有的成员时,可以使用 dynamic_cast
进行安全的类型转换。
#include
class Base {
public:
virtual void print() { std::cout << "Base" << std::endl; }
virtual ~Base() {}
};
class Derived : public Base {
public:
void print() override { std::cout << "Derived" << std::endl; }
void derivedFunction() { std::cout << "Derived function" << std::endl; }
};
int main() {
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast(basePtr);
if (derivedPtr) {
derivedPtr->derivedFunction();
}
delete basePtr;
return 0;
}
dynamic_cast
只能用于含有虚函数的类层次结构,因为它依赖于虚函数表来进行运行时类型检查。static_cast
static_cast
是一种编译时的类型转换,它可以用于各种基本类型之间的转换,以及在继承体系中进行向上转型(从派生类指针或引用转换为基类指针或引用)和向下转型(但不进行运行时检查)。
int
转 double
。#include
class Base {};
class Derived : public Base {};
int main() {
int num = 10;
double d = static_cast(num);
Derived derived;
Base* basePtr = static_cast (&derived); // 向上转型
return 0;
}
static_cast
不进行运行时类型检查,因此向下转型时如果类型不匹配,可能会导致未定义行为。const_cast
const_cast
主要用于去除或添加 const
或 volatile
修饰符。它只能用于改变对象的常量性或易变性,不能改变对象的类型。
当你需要在某些情况下修改一个原本被声明为 const
的对象时,可以使用 const_cast
去除 const
修饰符,但要确保这种修改是安全的。
#include
void printNonConst(int& num) {
std::cout << num << std::endl;
}
int main() {
const int num = 10;
int& nonConstNum = const_cast(num);
// nonConstNum = 20; // 不建议修改,可能导致未定义行为
printNonConst(nonConstNum);
return 0;
}
const
修饰符后修改原本为 const
的对象可能会导致未定义行为,应谨慎使用。reinterpret_cast
reinterpret_cast
是一种最危险的类型转换,它可以将任意指针类型转换为其他指针类型,甚至可以将指针转换为整数类型或反之。它不进行任何类型检查,只是简单地重新解释二进制位。