MST

星途 面试题库

面试题:C++ 友元函数访问权限在复杂模板和多线程场景下的行为

在 C++ 模板类中,友元函数的访问权限声明与普通类有何不同?当涉及多线程环境时,友元函数对类成员的访问可能会引发哪些问题?如何通过合适的设计和同步机制来避免这些问题?请给出详细的分析和代码示例。
39.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

友元函数访问权限声明在模板类与普通类中的不同

  1. 普通类
    • 在普通类中,友元函数声明相对直接。在类定义内部使用 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;
    }
    
  2. 模板类
    • 在模板类中,友元函数的声明会因模板参数而变得复杂。如果友元函数也是模板函数,需要提前声明该模板函数,然后在类中声明其为友元。例如:
    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;
    }
    

多线程环境下友元函数访问类成员可能引发的问题

  1. 数据竞争
    • 当多个线程同时调用友元函数访问类的成员时,可能会出现数据竞争。例如,假设类中有一个计数器成员变量,友元函数对其进行递增操作。如果多个线程同时执行该友元函数,可能会导致计数器的更新出现错误,因为多个线程可能同时读取计数器的值,然后进行递增操作,导致某些递增操作丢失。
  2. 不一致状态
    • 如果类的成员变量之间存在依赖关系,友元函数在多线程环境下可能会使类处于不一致状态。比如,一个类表示一个银行账户,有余额和交易记录两个成员,友元函数进行取款操作时,如果不同线程同时进行取款,可能会导致余额和交易记录不一致。

避免这些问题的设计和同步机制

  1. 互斥锁(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 的访问是线程安全的。
  2. 读写锁(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 用于写操作,保证写操作的原子性。