面试题答案
一键面试std::unique_ptr管理数组时的内存布局和底层实现机制
- 内存布局:
std::unique_ptr
管理数组时,其内部通常包含一个指向数组首元素的指针。例如,对于std::unique_ptr<int[]>
,它持有一个int*
类型的指针,指向动态分配的int
数组的起始位置。数组中的元素依次紧密排列在内存中,每个元素占用其自身类型大小的内存空间。
- 底层实现机制:
std::unique_ptr
采用RAII(Resource Acquisition Is Initialization)机制。当std::unique_ptr
对象被创建时,它获取(即指向)动态分配的数组内存。当std::unique_ptr
对象生命周期结束(例如超出作用域),其析构函数会自动释放所管理的数组内存。- 其实现通常依赖于
delete[]
操作符。例如,std::unique_ptr<int[]>
的析构函数中会调用delete[]
来释放int
数组的内存,确保内存正确释放,避免内存泄漏。
管理大量对象数组时优化std::unique_ptr的使用
- 使用内存池:
- 原理:内存池预先分配一块较大的连续内存空间,当需要创建对象数组时,从内存池中分配内存,而不是每次都调用
new[]
进行动态内存分配。这样可以减少内存碎片,因为内存池中的内存是连续的,并且减少了频繁的系统调用开销。 - 代码示例:
- 原理:内存池预先分配一块较大的连续内存空间,当需要创建对象数组时,从内存池中分配内存,而不是每次都调用
#include <iostream>
#include <memory>
#include <vector>
class MemoryPool {
public:
MemoryPool(size_t blockSize, size_t numBlocks)
: blockSize(blockSize), pool(new char[blockSize * numBlocks]), current(pool.get()) {}
void* allocate() {
if (current + blockSize > pool.get() + blockSize * numBlocks) {
return nullptr;
}
void* result = current;
current += blockSize;
return result;
}
private:
size_t blockSize;
std::unique_ptr<char[]> pool;
char* current;
};
class MyObject {
public:
MyObject() { std::cout << "MyObject constructed" << std::endl; }
~MyObject() { std::cout << "MyObject destructed" << std::endl; }
};
class ObjectAllocator {
public:
ObjectAllocator(size_t numObjects) : pool(sizeof(MyObject), numObjects) {}
MyObject* allocate() {
void* mem = pool.allocate();
if (mem) {
return new(mem) MyObject();
}
return nullptr;
}
void deallocate(MyObject* obj) {
obj->~MyObject();
}
private:
MemoryPool pool;
};
int main() {
ObjectAllocator allocator(100);
std::vector<std::unique_ptr<MyObject, decltype(&allocator.deallocate)>> objects;
for (size_t i = 0; i < 100; ++i) {
MyObject* obj = allocator.allocate();
if (obj) {
objects.emplace_back(obj, &allocator.deallocate);
}
}
return 0;
}
- 使用定制的删除器:
- 原理:定制删除器可以在释放对象数组时执行特定的清理逻辑,例如将内存返回内存池而不是直接调用
delete[]
。这可以提高内存管理的效率。 - 代码示例:
- 原理:定制删除器可以在释放对象数组时执行特定的清理逻辑,例如将内存返回内存池而不是直接调用
#include <iostream>
#include <memory>
class MyObject {
public:
MyObject() { std::cout << "MyObject constructed" << std::endl; }
~MyObject() { std::cout << "MyObject destructed" << std::endl; }
};
class MyDeleter {
public:
void operator()(MyObject* ptr) const {
// 定制的释放逻辑,这里简单演示直接delete
delete[] ptr;
}
};
int main() {
std::unique_ptr<MyObject[], MyDeleter> objArray(new MyObject[10]);
return 0;
}
将std::unique_ptr管理的数组传递给C语言接口
- 使用get()方法:
- 原理:
std::unique_ptr
的get()
方法返回其内部管理的指针。可以将这个指针安全地传递给C语言接口,因为C语言接口只接受常规指针。在传递后,需要注意C语言接口对指针的处理,确保不发生内存泄漏或悬空指针问题。 - 代码示例:
- 原理:
#include <iostream>
#include <memory>
extern "C" void cFunction(int* arr, size_t size) {
for (size_t i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
std::unique_ptr<int[]> arr(new int[5]{1, 2, 3, 4, 5});
cFunction(arr.get(), 5);
return 0;
}
在上述代码中,arr.get()
获取std::unique_ptr
管理的数组指针,并传递给cFunction
。cFunction
使用该指针访问数组元素,但不负责释放内存,内存仍由std::unique_ptr
在其生命周期结束时释放。