面试题答案
一键面试const修饰类成员变量的影响
- 内存布局:被
const
修饰的类成员变量在类的对象内存布局中有固定位置,且值一旦初始化后不可修改。 - 初始化要求:必须在类的构造函数初始化列表中进行初始化,因为其值不能在构造函数体中被赋值修改。
const修饰类成员函数的影响
- 对象状态:表明该成员函数不会修改对象的成员变量(除了
mutable
修饰的成员变量),这有助于调用者知道该函数不会改变对象的可观察状态。 - 调用限制:
const
对象只能调用const
成员函数,非const
对象可以调用const
和非const
成员函数。
利用const特性保证线程安全
- 不变性:如果一个类的成员函数被声明为
const
,并且在函数中没有对任何非mutable
成员变量进行修改,那么在多线程环境下,不同线程对该函数的调用不会导致数据竞争,因为函数不改变对象的状态。例如:
class MyClass {
public:
int getData() const {
return data;
}
private:
int data;
};
在多线程中,多个线程同时调用getData
函数不会出现数据竞争问题,因为该函数没有修改data
。
2. 互斥访问:结合互斥锁(如std::mutex
),可以进一步保证线程安全。例如:
class SafeClass {
public:
int getData() const {
std::lock_guard<std::mutex> lock(mutex_);
return data;
}
private:
mutable std::mutex mutex_;
int data;
};
这里getData
是const
函数,通过std::lock_guard
在访问data
前加锁,保证了多线程下的安全访问。虽然mutex_
需要在const
函数中修改状态,所以使用mutable
修饰。
const修饰类成员可能遇到的问题及解决方案
- 问题:当一个
const
成员函数需要更新对象的一些辅助数据结构(如缓存、计数器等)时,由于const
的限制不能直接修改成员变量,可能导致逻辑实现困难。例如:
class CachingClass {
public:
int getData() const {
if (!cacheValid) {
// 这里想更新cache和cacheValid,但在const函数中不允许
cache = expensiveCalculation();
cacheValid = true;
}
return cache;
}
private:
mutable bool cacheValid = false;
mutable int cache;
int expensiveCalculation() const;
};
- 解决方案:
- mutable关键字:将需要在
const
函数中修改的成员变量用mutable
修饰,如上述例子中的cacheValid
和cache
。这样可以在const
函数中修改这些变量,不影响函数的const
性质。 - 使用线程安全的缓存机制:如果缓存更新操作本身涉及多线程安全问题,除了使用
mutable
,还需结合互斥锁等同步机制。例如:
- mutable关键字:将需要在
class ThreadSafeCachingClass {
public:
int getData() const {
std::lock_guard<std::mutex> lock(mutex_);
if (!cacheValid) {
cache = expensiveCalculation();
cacheValid = true;
}
return cache;
}
private:
mutable std::mutex mutex_;
mutable bool cacheValid = false;
mutable int cache;
int expensiveCalculation() const;
};
通过互斥锁mutex_
保证了在多线程下对缓存更新操作的线程安全。