基于Proto3和单例模式的系统参数配置模块设计(附C++案例实现)

目录

  • [1 什么是Proto3?](#1 什么是Proto3?)
  • [2 Proto3基础语法](#2 Proto3基础语法)
    • [2.1 语法声明](#2.1 语法声明)
    • [2.2 包名(Package)](#2.2 包名(Package))
    • [2.3 导入语句(Import)](#2.3 导入语句(Import))
    • [2.4 注释](#2.4 注释)
    • [2.5 数据类型​​](#2.5 数据类型)
      • [2.5.1 标量类型(Scalar Types)](#2.5.1 标量类型(Scalar Types))
      • [2.5.2 复合类型](#2.5.2 复合类型)
    • [2.6 消息定义](#2.6 消息定义)
  • [3 单例设计模式](#3 单例设计模式)
    • [3.1 什么是单例模式?](#3.1 什么是单例模式?)
    • [3.2 饿汉单例模式](#3.2 饿汉单例模式)
    • [3.3 懒汉单例模式](#3.3 懒汉单例模式)
  • [4 案例](#4 案例)

1 什么是Proto3?

在分布式系统与微服务架构中,服务间数据通信的效率直接影响系统吞吐量、延迟和资源消耗。当 JSON、XML 等文本协议因冗余字符和低解析效率成为性能瓶颈时,Protocol Buffers(简称 Protobuf)作为 Google 开源的一种​​跨语言、平台无关的二进制序列化协议,核心功能是将结构化数据转换为紧凑的二进制流(序列化),以及从二进制流还原数据(反序列化),其工作流程基于​​预定义的 Schema​​(通过 .proto文件描述数据结构),并由 Protobuf 编译器(protoc)生成目标语言的数据访问类,屏蔽底层编码细节。Protobuf通过紧凑编码、强类型约束和跨语言支持,成为高吞吐、低延迟场景的主流选择。

2 Proto3基础语法

Proto3 文件以 .proto为后缀,核心结构包括​​语法声明​​、​​包名​​、​​导入语句​​、​​消息/枚举/服务定义​​和​​选项​​

2.1 语法声明

文件开头必须声明使用的 Proto 语法版本(目前主流为 proto3):

proto 复制代码
syntax = "proto3";  // 声明使用 Proto3 语法

2.2 包名(Package)

用于避免命名冲突,类似编程语言的命名空间:

proto 复制代码
package user.v1;  // 包名为 user.v1,生成代码时可映射为命名空间(如 Java 的 package)

2.3 导入语句(Import)

引入其他 .proto文件的定义(如标准库的 any.proto):

protobuf 复制代码
import "google/protobuf/any.proto";  // 导入 Any 类型定义
import "common/types.proto";          // 导入自定义公共类型

2.4 注释

支持单行(//)和多行(/* ... */)注释,与 C++ 一致:

protobuf 复制代码
// 单行注释:定义用户信息消息

/*
多行注释:
  包含用户 ID、姓名、年龄等基础字段
*/

2.5 数据类型​​

2.5.1 标量类型(Scalar Types)

标量类型是 Proto3 的基础数据类型,对应不同编程语言的原生类型(见下表):

Proto3 类型 描述 对应 C++ 类型 对应 Python 类型 取值范围/说明
double 双精度浮点数 double float 64 位 IEEE 754
float 单精度浮点数 float float 32 位 IEEE 754
int32 有符号 32 位整数 int32 int 变长编码(负数效率低,建议用 sint32)
int64 有符号 64 位整数 int64 int/long 变长编码(负数效率低,建议用 sint64)
uint32 无符号 32 位整数 uint32 int 变长编码
uint64 无符号 64 位整数 uint64 int/long 变长编码
sint32 有符号 32 位整数(优化) int32 int 变长编码(负数效率高)
sint64 有符号 64 位整数(优化) int64 int/long 变长编码(负数效率高)
fixed32 固定 32 位无符号整数 uint32 int 4 字节,值大时比 uint32 高效
fixed64 固定 64 位无符号整数 uint64 int/long 8 字节,值大时比 uint64 高效
sfixed32 固定 32 位有符号整数 int32 int 4 字节
sfixed64 固定 64 位有符号整数 int64 int/long 8 字节
bool 布尔值 bool bool true/false
string UTF-8 字符串 std::string str 长度 ≤ 2^32-1 字节
bytes 任意字节序列 std::string bytes 长度 ≤ 2^32-1 字节

2.5.2 复合类型

  • 消息(Message):自定义结构化数据类型(核心),可嵌套。
  • 枚举(Enum):定义命名的常量集合。
  • 键值对集合(Map):类似哈希表。

2.6 消息定义

消息是 Proto3 的核心,用于定义结构化数据。语法为:

protobuf 复制代码
message 消息名 {
  // 字段定义(类型 字段名 = 字段编号;)
  类型 字段名 = 字段编号;
  // ... 更多字段
}
  • 字段规则与编号

    • 字段规则:Proto3 仅支持 singular(默认,可省略)和 repeated(重复字段,类似数组),无 required/optional(所有字段默认 optional)。
    • 字段编号​​:1-15(占 1 字节)、16-2047(占 2 字节),不可重复,不可使用 19000-19999(Google 保留)。编号越小,编码效率越高。

    ​​示例:定义用户信息消息​​

    protobuf 复制代码
    message User {
     int32 id = 1;               // singular 字段(默认),用户 ID
     string name = 2;             // 用户名(UTF-8 字符串)
     bool is_active = 3;          // 是否激活
     repeated string emails = 4;  // repeated 字段,邮箱列表(可重复)
     bytes avatar = 5;            // 头像字节流
    }
  • 嵌套消息

    消息可嵌套定义(内部消息),支持多层嵌套

    示例

    protobuf 复制代码
    message Address {
     string street = 1;  // 街道
     string city = 2;    // 城市
     string zipcode = 3; // 邮编
    }
    
    message User {
     int32 id = 1;
     string name = 2;
     Address address = 3;  // 嵌套 Address 消息
     repeated Address addresses = 4;  // 多个地址(repeated 嵌套消息)
    }
  • Repeated 字段

    repeated修饰的字段表示"可重复",类似数组。Proto3 中默认使用 ​​packed 编码​​(连续存储元素,提升效率):

    示例

    protobuf 复制代码
    message Order {
      repeated int32 item_ids = 1;  // 商品 ID 列表(packed 编码)
      repeated Item items = 2;      // 商品详情列表(嵌套消息 repeated)
    }
  • Oneof 字段

    oneof定义一组互斥字段(同一时间仅一个字段有效),节省存储空间:

    示例

    protobuf 复制代码
    message SearchRequest {
      string query = 1;
      oneof result_type {
        int32 page_number = 2;  // 页码(与下面字段互斥)
        int32 offset = 3;       // 偏移量(与上面字段互斥)
      }
    }
  • Map 类型

    用形如map<key_type, value_type> map_name = 字段编号;定义键值对,key_type类型只能是​​标量类型​​(除 float/double/bytes),value_type类型任意:

    示例

    protobuf 复制代码
    message UserProfile {
     map<string, string> attributes = 1;  // 属性键值对(如 {"age": "30", "gender": "male"})
     map<int32, Address> region_addresses = 2;  // key 为区域 ID,value 为地址
    }
  • 枚举Enum ​​

    枚举定义命名的常量集合,语法为

    protobuf 复制代码
    enum 枚举名 {
      枚举值1 = 0;  // 必须包含 0 值(作为默认值)
      枚举值2 = 1;
      // ... 更多枚举值
    }
    protobuf 复制代码
    ​​示例:定义用户角色枚举​​
    enum UserRole {
      USER_ROLE_UNSPECIFIED = 0;  // 默认值(必须存在)
    USER_ROLE_ADMIN = 1;         // 管理员
    USER_ROLE_MEMBER = 2;        // 普通成员
      USER_ROLE_GUEST = 3;         // 访客
    }
    
    message User {
      int32 id = 1;
      UserRole role = 2;  // 使用枚举类型
    }

3 单例设计模式

3.1 什么是单例模式?

单例模式(Singleton Pattern)是一种设计模式,旨在确保一个类在整个应用程序中只有一个实例,并提供全局访问点。这种模式在需要唯一实例的场景中非常有用,比如配置管理器日志记录线程池数据库连接

实现单例模式的核心在于:

  • 私有构造函数且禁止拷贝
  • 静态的唯一实例

单例模式需要考虑线程安全问题,防止多个线程实例化多个单例,

3.2 饿汉单例模式

饿汉单例模式的核心特征是:在类加载时就创建唯一的实例(静态加载)。这种实现方式简单且线程安全------运行时的多线程还未产生,但可能会在类加载时浪费资源,因为实例即使在未被使用时也会被创建。

cpp 复制代码
template<typename TSingleton>
class Singleton {
public:
    using TSingletonPtr = std::unique_ptr<TSingleton>;

private:
    Singleton() = default;
    virtual ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    static TSingletonPtr instance;

public:
    static TSingletonPtr& Instance() {
        if (instance == nullptr) {
            instance.reset(new TSingleton());
        }
        return instance;
    }
};

template<typename TSingleton>
std::unique_ptr<TSingleton> Singleton<TSingleton>::instance = std::make_unique<TSingleton>();

应用案例:

cpp 复制代码
class Counter{
public:
    Counter() : cnt_(0) {};
    Counter(int cnt) : cnt_(cnt) {};
    ~Counter() = default;
    int counter() const { return cnt_; }

private:
    int cnt_;
};

int main() {
    std::cout << Singleton<Counter>::Instance()->counter();
    return 0;
}

3.3 懒汉单例模式

懒汉单例模式的核心特征是:在首次运行时需要时才创建该实例(动态加载),避免了不必要的资源消耗。它的实现相对简单,但需要处理线程安全问题以避免并发环境下的问题

在饿汉单例模式中,实例是静态的成员变量,所以需要在类加载时初始化;而在懒汉单例模式中,实例为局部静态变量,而局部静态变量具有延迟构造且线程安全(C++11保证)的特性,即运行时第一次调用Instance()时触发单例构造且只构造一次

cpp 复制代码
template<typename TSingleton>
class Singleton {
public:
    using TSingletonPtr = std::unique_ptr<TSingleton>;

private:
    Singleton() = default;
    virtual ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    static TSingletonPtr& Instance() {
        static TSingletonPtr instance = std::make_unique<TSingleton>();
        return instance;
    }
};

应用案例:

cpp 复制代码
class Counter{
public:
    Counter() : cnt_(0) {};
    Counter(int cnt) : cnt_(cnt) {};
    ~Counter() = default;
    int counter() const { return cnt_; }

private:
    int cnt_;
};

int main() {
    std::cout << Singleton<Counter>::Instance()->counter();
    return 0;
}

4 案例

首先,定义一个系统配置类

cpp 复制代码
class SystemConfig {
public:
  void initialize() {
	  std::string file_path(__FILE__);
	  size_t base_pos = file_path.rfind("/src/");
	  std::string base_dir = file_path.substr(0, base_pos);
	  std::string config_path = base_dir + "/system_config/system_config.pb.txt";
	  if (readTextProtoFile(config_path, &config_)) {
	    is_initialized_ = true;
	  } else {
	    R_WARN << "Read System configure file " << config_path << " failed.";
	  }
	}

  const pb::SystemConfig& configure();

private:
  bool is_initialized_{ false };
  pb::SystemConfig config_;
};

该类会读取基于proto定义的配置并初始化;用单例模板包装以保证系统配置在生命周期内只有一个实例

cpp 复制代码
using SystemConfigPtr = common::structure::Singleton<SystemConfig>;

接着,编写CMakeLists.txt来进行proto编译

cmake 复制代码
set(PROTO_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/protos)
file(MAKE_DIRECTORY ${PROTO_OUTPUT_DIR})

set(PROTO_SRCS "")
set(PROTO_HDRS "")

# system configure related protos
file(GLOB SYSTEM_CONFIG_PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/system_config/*.proto")
protobuf_generate(
  TARGET ${PROJECT_NAME}
  LANGUAGE cpp
  OUT_VAR SYSTEM_CONFIG_PROTO_SRCS
  PROTOS ${SYSTEM_CONFIG_PROTOS}
)
list(APPEND PROTO_SRCS ${SYSTEM_CONFIG_PROTO_SRCS})

include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)

target_link_libraries(${PROJECT_NAME}
  ${PROTOBUF_LIBRARIES}
  ${catkin_LIBRARIES}
)
target_include_directories(${PROJECT_NAME} PUBLIC
  ${CMAKE_CURRENT_BINARY_DIR}
)

这种写法可以使生成的.pb.cc以及.pb.h文件保持相对目录结构,在大型项目管理中比较方便。


🔥 更多精彩专栏

👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

相关推荐
Mintopia1 小时前
🌐 动态网络环境下的 WebAIGC 断点续传与容错技术
前端·人工智能·aigc
qinyia1 小时前
WisdomSSH解决因未使用Docker资源导致的磁盘空间不足问题
运维·服务器·人工智能·后端·docker·ssh·github
Stark-C1 小时前
凭实力出圈,头戴耳机的六边形战士!性价比拉满的iKF Mars实测
人工智能
哭泣方源炼蛊2 小时前
HAUE 新生周赛(七)题解
数据结构·c++·算法
paperxie_xiexuo2 小时前
面向多场景演示需求的AI辅助生成工具体系研究:十类平台的功能分型、技术实现与合规应用分析
大数据·人工智能·powerpoint·ppt
aneasystone本尊2 小时前
学习 LiteLLM 的缓存系统
人工智能
_OP_CHEN2 小时前
从零开始的Qt开发指南:(五)Qt 常用控件之 QWidget(上):解锁 Qt 界面开发的核心基石
开发语言·c++·qt·前端开发·qwidget·gui开发·qt常用控件
CNRio2 小时前
人工智能基础架构与算力之2 异构算力合池技术:打破资源壁垒的分布式 AI 部署方案
人工智能·分布式
Zlssszls2 小时前
全运会展现科技魅力,数字孪生打造智慧场馆新标杆
人工智能·科技·数字孪生·智慧场馆·全运会