1. 背景

在实际项目中,我们经常需要维护大量配置项,例如:

  • 串口地址
  • 网络地址
  • 标定参数
  • 传感器参数等

如果每个配置项都手写成员变量 + Getter + Setter,会产生大量重复代码,可维护性较差。因此,项目中引入了一个 ATTR 宏,用来自动生成“属性接口”,简化开发工作。本文结合实际项目代码,总结 ATTR 的设计原理、读写方式及正确使用方法。

2. ATTR 宏的定义

ATTR 生成的是“函数属性”,不是变量

  • 读:name()
  • 写:name(value)

ATTR优点

  • 大幅减少样板代码
  • 风格统一
  • 使用简单
  • 支持移动语义

ATTR缺点

  • 依赖宏,不易调试
  • IDE 跳转不友好
  • 扩展逻辑困难
  • 报错信息不直观
    适合内部工具类、配置类使用。

三个关键点

  1. 所有访问必须走函数
  2. 赋值只能在函数中完成
  3. 默认值 → ini 覆盖 → 运行使用 → 保存回写

2.1 ATTR.h

#ifndef ATTR_H
#define ATTR_H

/*
 * 使用前提:
 *  - ATTR 必须写在 class 的 private: 区域
 *  - 成员变量属于 private
 *  - Getter / Setter 自动提升为 public
 */

#define ATTR(Type, Name, Default)                                  \
Type _##Name = Default;                                       \
                                                                  \
public:                                                           \
    /* Getter */                                                   \
    const Type& Name() const noexcept { return _##Name; }         \
                                                                  \
    /* Setter(lvalue) */                                          \
    void Name(const Type& value) noexcept { _##Name = value; }    \
                                                                  \
    /* Setter(rvalue) */                                          \
    void Name(Type&& value) noexcept { _##Name = std::move(value); } \
private:

#endif // ATTR_H

2.2 ATTR 实际生成的代码

例如:

ATTR(std::string, sensorSeialPort, "COM3");

等价于:

private:
	std::string _sensorSeialPort = "COM3";
	
public:
	// Getter
	const std::string& sensorSeialPort() const {
		return _sensorSeialPort;
	}
	
	// Setter(拷贝)
	void sensorSeialPort(const std::string& v) {
		_sensorSeialPort = v;
	}
	
	// Setter(移动)
	void sensorSeialPort(std::string&& v) {
		_sensorSeialPort = std::move(v);
	}

private:

可以看到:

  • 生成了一个私有成员变量 _sensorSeialPort
  • 生成了一个 Getter
  • 生成了两个 Setter

ATTR 本质上就是一个“自动属性生成器”。

2.3 最小示例

class Test {
private:
	ATTR(int, age, 10)
};


int main() {
	Test t;
	
	// 读
	std::cout << t.age() << std::endl; // 10
	
	// 写
	t.age(20);
	
	std::cout << t.age() << std::endl; // 20
}

3. ATTR 使用示例

定义一个ConfigIni.hpp

class ConfigIni {
private:
	ATTR(int, age, 10);
	ATTR(double, upperAxisLimit,100.f);
	ATTR(double, downAxisLimit,100.f);
	ATTR(std::string, sensorSeialPort,"COM3"); // 默认初始化

void ReadData() {
	sensorSeialPort(ini.GetValue("Sensor", "SeialPort", "COM1"));
}

void SaveData() {
	ini.SetValue("Sensor", "SeialPort", sensorSeialPort().c_str());
}
};

3.1 ATTR 的基本使用规则

  1. 必须写在 private 区域
    ATTR 结尾自带 private:,因此必须放在类的 private 区域中。
    正确示例:

    class Config {
    private:
    	ATTR(int, age, 10)
    };
    
  2. ATTR 不是变量,是函数接口。使用 ATTR 后:

    • 不能直接访问成员变量
    • 必须通过函数访问
      这是理解 ATTR 的关键。

3.2 读取属性(Getter)

基本语法

name();

示例:

std::string addr = sensorSeialPort();
double limit = upperAxisLimit();

3.3 属性赋值(Setter)

基本语法

name(value);

像调用函数一样赋值。

  1. 字面量赋值

    sensorSeialPort("COM5");
    
  2. 变量赋值

    std::string s = "COM8";
    sensorSeialPort(s);
    

3.4 常见错误总结

  1. 当普通变量使用
    ❌ 错误:powerAddress = "COM3";
    ✅ 正确:powerAddress("COM3");
  2. 忘记加括号
    ❌ 错误:powerAddress;
    ✅ 正确:powerAddress();
  3. 试图通过返回值修改
    ❌ 错误:powerAddress() = "COM3";
    Getter 返回 const,禁止修改。
  4. 在类体中直接赋值
    ❌ 错误:powerAddress("COM1"); // 写在 class 里
    只能写在函数(构造函数 / 成员函数)中。
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐