面试题答案
一键面试内存管理可能遇到的问题
- 频繁内存分配与释放:每次向
vector<MyStruct>
中插入新元素时,若vector
容量不足,会重新分配内存,将原数据拷贝到新内存位置,导致性能开销。同样,删除元素时也可能涉及内存调整,若频繁操作,会有大量内存分配与释放的开销。 - 内存碎片化:由于
MyStruct
是大结构体,vector
动态增长和收缩过程中,频繁的内存分配和释放可能导致内存碎片化,降低内存利用率,后续内存分配可能会因找不到连续足够大的内存块而失败。 - 拷贝构造开销:当
vector
扩容重新分配内存时,会对MyStruct
对象进行拷贝构造,由于MyStruct
包含大量成员变量,拷贝构造开销较大。
通过定制内存分配器优化
定制内存分配器可以减少内存分配和释放的次数,提高内存使用效率。
#include <iostream>
#include <vector>
#include <memory>
// 自定义大结构体
struct MyStruct {
int data[1000]; // 大量成员变量示例
};
// 定制内存分配器
template <typename T>
class MyAllocator {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
template <typename U>
struct rebind {
using other = MyAllocator<U>;
};
MyAllocator() noexcept = default;
MyAllocator(const MyAllocator&) noexcept = default;
template <typename U>
MyAllocator(const MyAllocator<U>&) noexcept {}
~MyAllocator() noexcept = default;
pointer allocate(size_type n) {
if (n > std::numeric_limits<size_type>::max() / sizeof(T)) {
throw std::bad_alloc();
}
return static_cast<pointer>(std::malloc(n * sizeof(T)));
}
void deallocate(pointer p, size_type n) noexcept {
std::free(p);
}
};
// 必须提供比较函数,因为STL容器需要比较分配器
template <typename T1, typename T2>
bool operator==(const MyAllocator<T1>&, const MyAllocator<T2>&) noexcept {
return true;
}
template <typename T1, typename T2>
bool operator!=(const MyAllocator<T1>&, const MyAllocator<T2>&) noexcept {
return false;
}
使用定制内存分配器的vector
:
int main() {
std::vector<MyStruct, MyAllocator<MyStruct>> myVector;
MyStruct s;
myVector.push_back(s);
return 0;
}
解释:
- 定制内存分配器定义:
MyAllocator
模板类定义了内存分配和释放的方法。allocate
方法使用std::malloc
分配内存,deallocate
方法使用std::free
释放内存。 vector
使用定制分配器:通过std::vector<MyStruct, MyAllocator<MyStruct>>
定义vector
,使其使用定制的内存分配器,减少默认分配器频繁分配和释放内存带来的开销。
通过移动语义优化
移动语义可以避免对象的拷贝构造,提高插入和删除操作的性能。
#include <iostream>
#include <vector>
#include <memory>
// 自定义大结构体
struct MyStruct {
int data[1000]; // 大量成员变量示例
MyStruct() = default;
MyStruct(const MyStruct& other) {
std::copy(std::begin(other.data), std::end(other.data), std::begin(data));
}
MyStruct(MyStruct&& other) noexcept {
std::copy(std::begin(other.data), std::end(other.data), std::begin(data));
std::fill(std::begin(other.data), std::end(other.data), 0);
}
MyStruct& operator=(const MyStruct& other) {
if (this != &other) {
std::copy(std::begin(other.data), std::end(other.data), std::begin(data));
}
return *this;
}
MyStruct& operator=(MyStruct&& other) noexcept {
if (this != &other) {
std::copy(std::begin(other.data), std::end(other.data), std::begin(data));
std::fill(std::begin(other.data), std::end(other.data), 0);
}
return *this;
}
~MyStruct() = default;
};
int main() {
std::vector<MyStruct> myVector;
MyStruct s;
myVector.push_back(std::move(s)); // 使用移动语义
return 0;
}
解释:
- 移动构造函数和移动赋值运算符:在
MyStruct
结构体中定义了移动构造函数MyStruct(MyStruct&& other) noexcept
和移动赋值运算符MyStruct& operator=(MyStruct&& other) noexcept
。移动构造函数将资源从other
对象移动到当前对象,避免了深拷贝。 - 使用移动语义:在
main
函数中,通过myVector.push_back(std::move(s))
将MyStruct
对象std::move
到vector
中,利用移动语义避免了拷贝构造,提高了插入操作的性能。对于删除操作,vector
在析构时也会调用移动语义来处理对象,减少开销。