面试题答案
一键面试友元函数访问权限声明在模板类与普通类中的不同
- 普通类
- 在普通类中,友元函数声明相对直接。在类定义内部使用
friend
关键字声明友元函数,友元函数就可以访问该类的私有和保护成员。例如:
class MyClass { private: int privateData; public: MyClass(int data) : privateData(data) {} friend void printPrivateData(MyClass obj); }; void printPrivateData(MyClass obj) { std::cout << "Private data: " << obj.privateData << std::endl; }
- 在普通类中,友元函数声明相对直接。在类定义内部使用
- 模板类
- 在模板类中,友元函数的声明会因模板参数而变得复杂。如果友元函数也是模板函数,需要提前声明该模板函数,然后在类中声明其为友元。例如:
template <typename T> class TemplateClass; template <typename T> void printTemplatePrivateData(TemplateClass<T> obj); template <typename T> class TemplateClass { private: T privateData; public: TemplateClass(T data) : privateData(data) {} friend void printTemplatePrivateData<>(TemplateClass<T> obj); }; template <typename T> void printTemplatePrivateData(TemplateClass<T> obj) { std::cout << "Template private data: " << obj.privateData << std::endl; }
- 此外,模板类还支持非模板友元函数,这种情况下,友元函数对所有模板实例化都有访问权限。例如:
template <typename T> class AnotherTemplateClass { private: T privateData; public: AnotherTemplateClass(T data) : privateData(data) {} friend void printAnotherPrivateData(AnotherTemplateClass<int> obj); }; void printAnotherPrivateData(AnotherTemplateClass<int> obj) { std::cout << "Another private data (int only): " << obj.privateData << std::endl; }
多线程环境下友元函数访问类成员可能引发的问题
- 数据竞争
- 当多个线程同时调用友元函数访问类的成员时,可能会出现数据竞争。例如,假设类中有一个计数器成员变量,友元函数对其进行递增操作。如果多个线程同时执行该友元函数,可能会导致计数器的更新出现错误,因为多个线程可能同时读取计数器的值,然后进行递增操作,导致某些递增操作丢失。
- 不一致状态
- 如果类的成员变量之间存在依赖关系,友元函数在多线程环境下可能会使类处于不一致状态。比如,一个类表示一个银行账户,有余额和交易记录两个成员,友元函数进行取款操作时,如果不同线程同时进行取款,可能会导致余额和交易记录不一致。
避免这些问题的设计和同步机制
- 互斥锁(
std::mutex
)- 使用
std::mutex
可以确保在同一时间只有一个线程能够访问类的成员。例如:
#include <iostream> #include <mutex> #include <thread> class ThreadSafeClass { private: int data; std::mutex mtx; public: ThreadSafeClass(int value) : data(value) {} friend void incrementData(ThreadSafeClass& obj); }; void incrementData(ThreadSafeClass& obj) { std::lock_guard<std::mutex> lock(obj.mtx); obj.data++; std::cout << "Incremented data: " << obj.data << std::endl; } int main() { ThreadSafeClass obj(0); std::thread t1(incrementData, std::ref(obj)); std::thread t2(incrementData, std::ref(obj)); t1.join(); t2.join(); return 0; }
- 在上述代码中,
std::lock_guard
会在构造时自动锁定互斥锁mtx
,在析构时自动解锁,从而保证incrementData
函数在多线程环境下对data
的访问是线程安全的。
- 使用
- 读写锁(
std::shared_mutex
)- 如果友元函数主要是读取类的成员数据,偶尔进行写入操作,可以使用读写锁。读操作可以并发执行,而写操作会独占锁。例如:
#include <iostream> #include <shared_mutex> #include <thread> class ReadWriteSafeClass { private: int data; std::shared_mutex mtx; public: ReadWriteSafeClass(int value) : data(value) {} friend void readData(const ReadWriteSafeClass& obj); friend void writeData(ReadWriteSafeClass& obj, int newData); }; void readData(const ReadWriteSafeClass& obj) { std::shared_lock<std::shared_mutex> lock(obj.mtx); std::cout << "Read data: " << obj.data << std::endl; } void writeData(ReadWriteSafeClass& obj, int newData) { std::unique_lock<std::shared_mutex> lock(obj.mtx); obj.data = newData; std::cout << "Written data: " << obj.data << std::endl; } int main() { ReadWriteSafeClass obj(0); std::thread t1(readData, std::ref(obj)); std::thread t2(writeData, std::ref(obj), 10); t1.join(); t2.join(); return 0; }
- 这里
std::shared_lock
用于读操作,允许多个线程同时读取,std::unique_lock
用于写操作,保证写操作的原子性。