内联函数在C++中是一种特殊的函数优化机制,它的核心思想是通过函数体替换函数调用来消除函数调用开销。当我们在函数声明前加上inline关键字时,就是在向编译器建议将这个函数处理为内联函数。但需要注意,inline只是一个建议,编译器会根据函数体大小、调用频率等因素自主决定是否真正内联。
从底层实现来看,内联函数与普通函数的关键区别在于:
典型的内联函数使用场景包括:
重要提示:过度使用内联可能导致代码膨胀,特别是在递归或大型函数的情况下。现代编译器通常能比程序员更好地决定何时使用内联。
很多面试者容易混淆内联函数和宏定义,实际上二者有本质区别:
| 特性 | 内联函数 | 宏定义 |
|---|---|---|
| 处理阶段 | 编译期 | 预处理期 |
| 类型检查 | 有 | 无 |
| 调试支持 | 支持 | 不支持 |
| 副作用 | 无 | 可能产生 |
| 复杂表达式 | 支持 | 需要特殊处理 |
宏定义最常见的陷阱是参数多次求值问题。例如:
cpp复制#define SQUARE(x) ((x)*(x))
int a = 1;
int b = SQUARE(a++); // 结果是2而不是1
而用内联函数实现则不会有这个问题:
cpp复制inline int square(int x) { return x * x; }
int a = 1;
int b = square(a++); // 正确得到1
C++17引入了inline变量的概念,使得头文件中定义变量更加规范。对于内联函数,现代编译器的处理策略也变得更加智能:
在实际工程中,我发现以下经验特别有用:
C++中的new和delete运算符远比表面看起来复杂。一次简单的new T操作实际上经历了以下步骤:
对应的delete操作则是:
面试常考的点在于operator new的重载。我们可以全局重载,也可以为特定类重载:
cpp复制class Widget {
public:
static void* operator new(size_t size) {
std::cout << "Custom new for Widget, size: " << size << "\n";
return ::operator new(size);
}
static void operator delete(void* p) {
std::cout << "Custom delete for Widget\n";
::operator delete(p);
}
};
内存对齐对性能有重大影响。考虑以下结构体:
cpp复制struct BadLayout {
char c; // 1字节
double d; // 8字节
int i; // 4字节
}; // 可能占用24字节(取决于平台)
struct GoodLayout {
double d; // 8字节
int i; // 4字节
char c; // 1字节
}; // 通常占用16字节
C++11引入了alignas关键字来指定对齐要求:
cpp复制struct alignas(64) CacheLine {
int data[16];
}; // 确保整个结构体占用一个缓存行
在实际项目中,我总结的对齐经验包括:
虽然智能指针大大简化了内存管理,但仍有不少需要注意的细节:
cpp复制struct Node {
shared_ptr<Node> next;
// 添加 weak_ptr<Node> prev; 来打破循环
};
cpp复制unique_ptr<Resource> createResource() {
return unique_ptr<Resource>(new Resource());
}
void consumeResource(unique_ptr<Resource> res) {
// 获取资源所有权
}
一个实用的技巧是使用shared_ptr的别名构造函数:
cpp复制struct Value {
int id;
string name;
};
auto p = make_shared<Value>();
shared_ptr<string> namePtr(p, &p->name); // 共享所有权但指向成员
虚函数是C++多态的核心实现机制。每个包含虚函数的类都有一个虚函数表(vtable),对象中包含指向该表的指针(vptr)。考虑以下继承体系:
cpp复制class Base {
public:
virtual void foo() {}
virtual void bar() {}
};
class Derived : public Base {
public:
void foo() override {}
void baz() {}
};
内存布局大致如下:
code复制Base vtable:
Base::foo()
Base::bar()
Derived vtable:
Derived::foo() // 重写
Base::bar() // 继承
面试中常被问到的要点包括:
移动语义是C++11最重要的特性之一。一个完整的资源管理类通常需要实现"五法则":
cpp复制class ResourceHolder {
int* data;
size_t size;
public:
// 构造函数
explicit ResourceHolder(size_t sz) : size(sz), data(new int[sz]) {}
// 1. 析构函数
~ResourceHolder() { delete[] data; }
// 2. 拷贝构造函数
ResourceHolder(const ResourceHolder& other)
: size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
}
// 3. 拷贝赋值运算符
ResourceHolder& operator=(const ResourceHolder& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// 4. 移动构造函数
ResourceHolder(ResourceHolder&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 5. 移动赋值运算符
ResourceHolder& operator=(ResourceHolder&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
在面试中,常被要求解释std::move的本质(它只是将左值转换为右值引用,并不实际移动任何东西)。
C++中的异常安全保证分为三个级别:
实现强保证的典型模式是"copy-and-swap":
cpp复制class String {
char* data;
size_t length;
void swap(String& other) noexcept {
std::swap(data, other.data);
std::swap(length, other.length);
}
public:
String& operator=(const String& other) {
String temp(other); // 可能抛出异常
swap(temp); // 不抛操作
return *this;
}
String& operator=(String&& other) noexcept {
swap(other);
return *this;
}
};
在面试中,常被要求分析某段代码的异常安全性,或者设计具有特定异常安全保证的接口。
现代CPU的缓存机制对程序性能有巨大影响。一个典型的缓存优化例子是矩阵乘法:
cpp复制// 低效版本(缓存不友好)
void multiply(const vector<vector<int>>& a,
const vector<vector<int>>& b,
vector<vector<int>>& result) {
for (size_t i = 0; i < a.size(); ++i)
for (size_t k = 0; k < a[0].size(); ++k)
for (size_t j = 0; j < b[0].size(); ++j)
result[i][j] += a[i][k] * b[k][j];
}
// 优化版本(缓存友好)
void multiplyOptimized(const vector<vector<int>>& a,
const vector<vector<int>>& b,
vector<vector<int>>& result) {
for (size_t i = 0; i < a.size(); ++i)
for (size_t j = 0; j < b[0].size(); ++j)
for (size_t k = 0; k < a[0].size(); ++k)
result[i][j] += a[i][k] * b[k][j];
}
看似只是循环顺序变化,但性能差异可能达到数倍。这是因为优化版本更好地利用了空间局部性。
其他缓存优化技巧包括:
现代CPU使用分支预测来提升流水线效率。可以通过以下方式帮助CPU更好地预测:
cpp复制// 优化前
if (unlikely_condition) {
// 处理异常情况
} else {
// 正常流程
}
// 优化后
if (likely_condition) {
// 正常流程
} else {
// 处理异常情况
}
cpp复制// 传统实现
int max(int a, int b) {
return a > b ? a : b;
}
// 无分支实现
int max(int a, int b) {
return a ^ ((a ^ b) & -(a < b));
}
cpp复制#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
if (unlikely(error_condition)) {
// 错误处理
}
C++11引入了标准内存模型,解决了多线程环境下的内存可见性问题。关键概念包括:
cpp复制// 自旋锁实现
class SpinLock {
atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while (flag.test_and_set(memory_order_acquire));
}
void unlock() {
flag.clear(memory_order_release);
}
};
在实际项目中,我的经验是: