导读:本期聚焦于小伙伴创作的《C++的friend声明有哪些合理的用例?单元测试与序列化场景怎么用》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++的friend声明有哪些合理的用例?单元测试与序列化场景怎么用》有用,将其分享出去将是对创作者最好的鼓励。

C++的friend声明用于赋予指定的函数、类或者其他类成员函数访问当前类私有成员和保护成员的权限,在合理场景下使用可以平衡封装性和功能需求,其中单元测试和序列化是两个非常典型且合理的应用场景。

C++的friend声明有哪些合理的用例?单元测试与序列化场景怎么用

friend声明的基本语法

friend声明需要写在类的内部,可以是友元函数、友元类或者友元成员函数,语法形式如下:

// 友元函数
class MyClass {
private:
    int value;
public:
    MyClass(int v) : value(v) {}
    // 声明外部函数为友元,允许其访问私有成员value
    friend void printValue(const MyClass& obj);
};

void printValue(const MyClass& obj) {
    // 可以直接访问obj的私有成员value
    printf("value is %dn", obj.value);
}

// 友元类
class Helper {
public:
    void modifyValue(MyClass& obj, int newVal) {
        obj.value = newVal; // 可以访问MyClass的私有成员
    }
};

class MyClass {
private:
    int value;
    // 声明Helper类为友元类,Helper的所有成员函数都可以访问MyClass的私有成员
    friend class Helper;
public:
    MyClass(int v) : value(v) {}
};

单元测试场景下的合理用例

单元测试的核心目标是验证类的内部逻辑是否正确,很多时候需要检查类的私有成员状态是否符合预期,如果为了测试把私有成员改成公有或者提供公有的getter方法,会破坏类的封装性,此时使用friend声明是更合理的选择。

通常我们会把测试类或者测试函数声明为被测试类的友元,让测试代码可以直接访问私有成员完成验证,同时不影响生产代码对外的封装性。

#include <cassert>

// 被测试的类,封装了内部计数逻辑
class Counter {
private:
    int count; // 私有计数成员
public:
    Counter() : count(0) {}
    void increment() {
        count++;
    }
    void decrement() {
        count--;
    }
    // 声明测试函数为友元,仅测试代码可以访问count
    friend void testCounter();
};

// 单元测试函数
void testCounter() {
    Counter c;
    c.increment();
    c.increment();
    // 直接访问私有成员count验证结果,不需要额外暴露公有接口
    assert(c.count == 2);
    c.decrement();
    assert(c.count == 1);
    printf("Counter test passedn");
}

这种方式的好处是,生产代码中Counter类的count成员依然是私有的,外部业务代码无法直接修改,只有测试函数可以访问,既满足了测试需求,又不会破坏封装性。

序列化场景下的合理用例

序列化是指将对象的状态转换为可存储或可传输的格式,反序列化则是从存储的格式还原对象状态。很多时候对象的内部状态是私有的,序列化逻辑需要读写这些私有成员,此时使用friend声明可以让序列化相关的函数访问私有成员,而不需要为所有私有成员提供公有的getter和setter。

常见的做法是将序列化、反序列化函数声明为类的友元,或者将负责序列化的工具类声明为友元。

#include <cstdio>
#include <cstring>

// 需要序列化的用户类
class User {
private:
    char name[32];
    int age;
public:
    User(const char* n, int a) {
        strncpy(name, n, sizeof(name)-1);
        name[sizeof(name)-1] = '';
        age = a;
    }
    // 声明序列化函数为友元
    friend void serializeUser(const User& user, FILE* file);
    // 声明反序列化函数为友元
    friend User deserializeUser(FILE* file);
};

// 序列化函数,将User对象写入文件
void serializeUser(const User& user, FILE* file) {
    // 直接访问私有成员写入文件
    fwrite(user.name, sizeof(char), 32, file);
    fwrite(&user.age, sizeof(int), 1, file);
}

// 反序列化函数,从文件还原User对象
User deserializeUser(FILE* file) {
    char tmpName[32];
    int tmpAge;
    fread(tmpName, sizeof(char), 32, file);
    fread(&tmpAge, sizeof(int), 1, file);
    // 这里需要友元吗?如果是通过构造函数初始化则不需要,但如果要修改私有成员则需要
    // 假设我们有一个内部初始化函数,或者反序列化函数作为友元直接赋值
    User u("", 0);
    // 直接修改私有成员完成反序列化
    strncpy(u.name, tmpName, 32);
    u.name[31] = '';
    u.age = tmpAge;
    return u;
}

int main() {
    User u1("张三", 25);
    FILE* f = fopen("user.dat", "wb");
    serializeUser(u1, f);
    fclose(f);

    f = fopen("user.dat", "rb");
    User u2 = deserializeUser(f);
    fclose(f);
    printf("Deserialized user: name=%s, age=%dn", u2.name, u2.age);
    return 0;
}

如果不使用friend声明,要么需要把name和age改成公有,要么需要给它们添加公有的访问接口,这都会让类的内部状态暴露给所有外部代码,而使用friend声明后,只有序列化相关的函数可以访问这些私有成员,其他外部代码依然无法直接操作,保证了封装性。

使用friend声明的注意事项

虽然friend声明在单元测试和序列化场景下是合理的,但也不能滥用,需要遵循以下原则:

  • 只给确实需要访问私有成员的特定函数或类授予友元权限,不要随意把无关的类声明为友元
  • 友元关系是单向的,比如A是B的友元,不代表B是A的友元
  • 友元关系不能被继承,父类的友元不会自动成为子类的友元
  • 优先通过公有接口实现功能,只有当公有接口无法满足需求且会破坏封装性时,才考虑使用friend声明

总的来说,friend声明不是破坏封装性的工具,而是在特定场景下平衡封装性和功能需求的合理手段,在单元测试和序列化场景中合理使用,可以让代码既安全又满足功能要求。

friend声明单元测试序列化C++修改时间:2026-07-05 21:24:27

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。