CppCon 2015 学习:Implementing class properties effectively

这段内容讲的是C++中“属性”(Property)的实现及其设计理念,并结合一个实际类Text来说明。中文理解如下:

关于“属性”(Property)

  • 属性:介于类的字段(field)和方法(method)之间的东西。
    也就是说,属性看起来像成员变量,但可以通过方法(getter/setter)控制访问和赋值,实现更灵活的封装和控制。

代码示例类 Text

class Text : public BaseElement
{
public:ZOBJ(Text);                   // 可能是宏定义,做一些元编程的辅助Text() : string(nullptr) { }  // 构造函数,初始化 string 为 nullptrint align;                    // 对齐方式,属性示例之一ZString *string;              // 字符串指针int stringLength;             // 字符串长度float drawOffsetY;            // 绘制时的Y轴偏移FontGeneric *font;            // 字体对象指针float wrapWidth;              // 自动换行宽度DynamicArray *formattedStrings; // 格式化字符串数组DynamicArray *multiDrawers;   // 多重绘制器float maxHeight;              // 最大高度bool wrapLongWords;           // 是否允许长词换行virtual Text *initWithFont(FontGeneric *i);  // 初始化方法...
};

理解总结

  • 这个类中列出了一些“属性”字段,比如alignstringwrapWidth等。
  • 这些“属性”很直观地是类的数据成员,但实际项目中,想把这些直接字段换成属性(比如封装成getter/setter)可以增强安全性和灵活性。
  • 这正是“属性”的本质:外观是数据成员,但实际上是通过方法控制访问的接口。
  • 你可以用类似C#的属性语法来模拟,或者用宏/模板等方式来简化写法。

这段代码体现了大量遗留代码(legacy code)的实际用法和风格,中文理解如下:

代码分析

Text* text;
...
text->setText(ZS(STR_LOC_OMNOM));
...
text->width = 42;
text->height = GetQuadOffset(IMG_OMNOM__top_offs).y;
...
text->draw();

理解总结

  • text 是指向 Text 类实例的指针。
  • text->setText(ZS(STR_LOC_OMNOM));
    这是通过方法(setter)设置文本内容,ZS(STR_LOC_OMNOM) 可能是某种字符串宏或转换函数。
  • text->width = 42;text->height = ...;
    直接通过公有成员变量访问和赋值宽高。
    这里是“直接访问字段”的风格,缺乏封装性和安全性。
  • text->draw();
    调用对象的方法进行绘制。

说明

  • 这种混合用法:部分用方法访问(setText()),部分直接访问字段(width, height)是典型的遗留代码风格。
  • 直接字段访问容易导致难以控制属性的变化,也难以插入额外逻辑(如验证、事件通知等)。
  • 这也是为什么现代代码倾向于将“属性”封装为 getter/setter 或类似机制。

1. 需求背景

想模拟C#里“属性”(Property)的语法:

foo.size = 10; // 实际调用setter
int x = foo.size; // 实际调用getter

而不是直接访问成员变量。

2. 最简单的实现——成员变量直接暴露

class Bar {
public:int size;
};

用法:

Bar bar;
bar.size = 10;  // 直接赋值成员变量

缺点:不能拦截赋值或读取操作,无法封装额外逻辑(例如校验、触发事件等)。

3. 用getter/setter封装访问

class Foo {
private:int size;
public:void setSize(const int& s) { size = s; }const int& getSize() const { return size; }
};

用法:

Foo foo;
foo.setSize(10);       // 设置
int x = foo.getSize(); // 读取

缺点:调用写法不够简洁。

4. Property模板类模拟属性语法糖

想实现:

foo.size = 10;  // 自动调用setSize
int x = foo.size; // 自动调用getSize

这需要通过C++运算符重载和隐式类型转换来实现。

5. 纯存储型Property(不满足调用宿主方法需求)

template<typename T>
class Property {
private:T value;
public:Property<T>& operator=(const T& v) {value = v;return *this;}operator const T&() {return value;}
};
  • 赋值重载=operator=,赋值给内部成员
  • 隐式转换operator T&(),读取时返回内部成员引用
    缺点
  • 不能调用宿主对象的方法,不能实现额外逻辑。

6. 用std::function封装getter/setter,满足调用宿主函数需求

template<typename T>
class Property {
private:std::function<void(const T&)> setter;std::function<const T&()> getter;
public:Property(std::function<void(const T&)> s, std::function<const T&()> g): setter(s), getter(g) {}Property<T>& operator=(const T& v) {setter(v);return *this;}operator const T&() {return getter();}
};

用法示例:

class Test {
private:float dimension;
public:const float& getArea() const {// 假设返回引用可能不安全,这里简化示例static float area = dimension * dimension;return area;}void setArea(const float& val) {dimension = std::sqrt(val);}Property<float> area;Test() : area([this](const float& v){ setArea(v); },[this]() -> const float& { return getArea(); }) {}
};
  • Property对象内部保存两个std::function,指向宿主对象的方法。
  • 赋值时调用setter,读取时调用getter。

缺点

  • std::function内部存储函数对象(闭包),开销较大,至少有堆分配和类型擦除,sizeof(Property)通常较大(例如32字节)。
  • 每次调用通过std::function间接,性能下降。

7. 用成员函数指针(member function pointers)优化

成员函数指针示例:

struct Test {void setVal(const int& v) { /* ... */ }const int& getVal() const { /* ... */ return val; }int val;
};
  • 成员函数指针类型:
    • void (Test::*)(const int&) ——指向成员函数void setVal(const int&)
    • const int& (Test::*)() const ——指向成员函数const int& getVal() const

8. 结合成员函数指针写Property模板

template <typename T, typename Host>
class Property {
private:void (Host::*setter)(const T&);const T& (Host::*getter)() const;Host* host;
public:Property(void (Host::*set)(const T&), const T& (Host::*get)() const, Host* h): setter(set), getter(get), host(h) {}Property<T, Host>& operator=(const T& value) {(host->*setter)(value);  // 调用宿主setterreturn *this;}operator const T&() const {return (host->*getter)(); // 调用宿主getter}
};

优点:

  • Property对象只存储3个指针(setter、getter成员函数指针 + 指向宿主实例的指针),通常24字节。
  • std::function轻量许多。
  • 调用时通过成员函数指针调用宿主函数,调用开销更低。

缺点:

  • 调用时仍有两次间接调用:
    • host->*setter,调用成员函数指针;
    • 成员函数调用本身也有一定调用开销(尤其虚函数)。

9. 进一步优化思考:利用成员指针偏移

成员指针本质上是宿主类对象中某成员的偏移(对数据成员而言),或成员函数的地址。
如果只操作数据成员(不是调用函数),我们可以将“成员指针”当做偏移量,在编译期知道偏移量,直接用指针+偏移访问成员数据。
比如:

template<typename T, typename Host, T Host::*member>
class Property {
private:Host* host;
public:Property(Host* h) : host(h) {}Property<T, Host, member>& operator=(const T& value) {host->*member = value;  // 直接访问成员变量return *this;}operator const T&() const {return host->*member;   // 直接访问成员变量}
};

10. 将“调用宿主方法”的逻辑加入

如果你想要“赋值时调用宿主的setter函数”,读取时调用getter函数,而不直接访问成员变量,需要你在Host类中写统一的接口,比如:

struct Foo {int size_;void setSize(const int& v) { size_ = v; }const int& getSize() const { return size_; }
};

配合:

template<typename T, typename Host, T Host::*member>
class Property {
private:Host* host;
public:Property(Host* h) : host(h) {}Property<T, Host, member>& operator=(const T& value) {host->setSize(value);  // 这里硬编码了setSize,不能通用return *this;}operator const T&() const {return host->getSize();}
};

但这显然不通用,也不能自动匹配getter/setter,除非借助宏或者模板技巧(如成员函数指针传参)。

11. 性能对比总结

方案内存大小(示例)调用层数优点缺点
纯成员变量sizeof(T)0简单快速不能调用getter/setter
Property + std::function~32字节3灵活,可任意函数封装大开销,调用慢
Property + 成员函数指针~24字节2较轻量,调用宿主函数调用间接层依旧存在
Property + 成员变量指针(偏移)~8字节1轻量,调用快只能直接访问数据成员

12. 实际应用示例

struct Foo {int size_;void setSize(const int& v) { size_ = v; }const int& getSize() const { return size_; }
};
template<typename T, typename Host>
class Property {
private:void (Host::*setter)(const T&);const T& (Host::*getter)() const;Host* host;
public:Property(void (Host::*set)(const T&), const T& (Host::*get)() const, Host* h): setter(set), getter(get), host(h) {}Property<T, Host>& operator=(const T& value) {(host->*setter)(value);return *this;}operator const T&() const {return (host->*getter)();}
};
int main() {Foo foo;Property<int, Foo> sizeProp(&Foo::setSize, &Foo::getSize, &foo);sizeProp = 10;               // 调用foo.setSize(10)std::cout << int(sizeProp); // 调用foo.getSize()
}

13. 总结

  • 模拟属性的最佳方案取决于效率与灵活性的权衡:
    • 最高效的是直接数据成员指针操作,但无法封装逻辑。
    • 最灵活的是std::function,但内存占用和运行时开销大。
    • 成员函数指针折中方案较常用,性能和灵活性都较好。
  • C++目前没有原生支持属性语法,只能靠重载运算符模拟。
  • 你若追求最高效率,可以用模板配合成员指针偏移和宿主类接口,消除动态调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[electron]预脚本不显示内联script

script-src self 是 Content Security Policy (CSP) 中的一个指令&#xff0c;它的作用是限制加载和执行 JavaScript 脚本的来源。 具体来说&#xff1a; self 表示 当前源。也就是说&#xff0c;只有来自当前网站或者当前页面所在域名的 JavaScript 脚本才被允许执行。"…

基于安卓的文件管理器程序开发研究源码数据库文档

摘 要 伴随着现代科技的发展潮流&#xff0c;移动互联网技术快速发展&#xff0c;各种基于通信技术的移动终端设备做的也越来越好了&#xff0c;现代智能手机大量的进入到了我们的生活中。电子产品的各种软硬技术技术的发展&#xff0c;操作系统的不断更新换代&#xff0c;谷歌…

MySQL主从复制实现指南

MySQL主从复制实现指南 一、主从复制原理 #mermaid-svg-i1zOswdD4OORQ35t {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i1zOswdD4OORQ35t .error-icon{fill:#552222;}#mermaid-svg-i1zOswdD4OORQ35t .error-text…

spring jms使用

文章目录 一、背景介绍二、注意点说明三、代码pom.xmlmq.propertiesActiveMqConfigJmsSenderActiveMqInfoActivemqProtocolEnum生产者消费者 一、背景介绍 <dependency> 标签中列出的 spring-jms 是 Spring Framework 提供的一个模块&#xff0c;用于简化 Java Message …

笔记——学习HTTP协议

HTTP协议 文章目录 HTTP协议一、统一资源定位符&#xff08;url&#xff09;&#xff1a;URL编码格式&#xff1a; 二、HTTP报文结构&#xff1a;三、HTTP请求方法&#xff1a;四、HTTP常见请求头&#xff1a;五、HTTP状态码&#xff1a;六、HTTP常见响应头&#xff1a;七、HTT…

Unity中的Mathf.Clamp01

2025年6月8日&#xff0c;周日晚上 Mathf.Clamp01 是 Unity 中的一个数学函数&#xff0c;它的作用是将一个浮点数&#xff08;float&#xff09;限制在0 到 1的范围内。具体来说&#xff1a; 如果输入值 小于 0&#xff0c;则返回 0。 如果输入值 大于 1&#xff0c;则返回 1…

1 Studying《Linux Media Documentation》

目录 1 MEDIA SUBSYSTEM ADMIN AND USER GUIDE 1.1.2 Building support for a media device 1.1.3 Infrared remote control support in video4linux drivers 1.1.4 Digital TV 1.1.5 Cards List 1.1.5.2 PCI drivers 1.1.6 Video4Linux (V4L) driver-specifific docume…

编译原理 学习 2025年6月10日11:17:54

编译原理 将高级编程语言编写的源代码转换成机器可执行的代码(二进制或汇编代码) 核心任务: 词法分析(正则表达式和有限自动机): 示例Token分类&#xff1a;关键字&#xff1a;if, while 运算符&#xff1a;, 标识符&#xff1a;变量名 分解源代码为单词 识别 其中关键字 …

风中低语:Linux 信号处理的艺术与实践

文章目录 &#x1f307;前言&#x1f3d9;️正文1、信号的处理时机1.1、处理情况1.2、“合适” 的时机 2、用户态与内核态2.1、概念2.2、重谈进程地址空间2.3、信号的处理过程 3、信号的捕捉3.1、内核如何实现信号的捕捉&#xff1f;3.2、sigaction 4、信号部分小结 补充 5、可…

ASP.NET Core SignalR - 部分客户端消息发送

文章目录 前言一、消息发送的核心概念1.客户端标识2.消息接收范围 二、向特定用户发送消息管理员向指定用户发送私信&#xff0c;或用户之间一对一聊天。 三、向组发送消息聊天室、工作群组、通知订阅等。 四、广播消息系统公告、实时统计数据更新等。 五、向角色发送消息向管理…

前后端交互过程中—各类文件/图片的上传、下载、显示转换

前后端交互过程中—各类文件/图片的上传、下载、显示转换 图片上传下载常用函数&#xff1a;new Blob()**blobParts&#xff1a;&#xff08;必传&#xff09;****options&#xff1a;&#xff08;可选&#xff09;**blob的常见的MIME类型&#xff1a; URL.createObjectURL()替…

校园二手交易平台(微信小程序版)

文章目录 1. 项目概述2. 项目功能思维导图3. 技术架构1. 前端技术栈2. 后端技术栈 4. 核心模块实现5. 总结6. 项目实现效果截图7. 关于作者其它项目视频教程介绍 1. 项目概述 校园二手交易平台微信小程序旨在为在校学生提供一个便捷的二手物品交易渠道&#xff0c;包含用户模块…

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …

【芯片设计- RTL 数字逻辑设计入门 4.2 -- 组合逻辑赋值 + 时序逻辑状态保持】

文章目录 Overview原语句分析变量含义假设(根据命名推测)状态更新逻辑详解状态转移逻辑举个实际例子小结Overview 本文将详细介绍 verilog rtl 中 assign reg_halt_mode_nx = halt_taken | (reg_halt_mode & ~halt_return);的作用,以及这里为何要使用 reg_halt_mode,…

【单片机期末】汇编试卷

一、选择题 DPTR是16位的&#xff0c;所以寻址范围是64KB R1是8位的&#xff0c;只能寻址256 访问内部ROM只能用MOVC指令 一个指令周期是时钟周期的1/12 12个时钟周期是一个机器周期 单指令周期是指一个机器周期 T 1 / f 12MHz ~ 1us 13位计数16位计数8位自动重装载双8位计数器…

校验枚举类类型的入参合法性的统一方案

文章目录 背景解决实践定义枚举类 InEnum注解定义验证逻辑 InEnumValidator 实际使用 背景 业务要做电商平台做入参, 在电商平台被抽离成枚举类的情况下 &#xff0c;要怎么验证输入的参数是正确的呢? 解决 Constraint 实现自定义验证逻辑 Constraint 注解用于标注其他注解&am…

Unity-NavMesh详解-其一

今天我们来详细地探究一下Unity的NavMesh这一性能强大的组件&#xff1a; NavMesh基本使用 NavMesh简单地说本质上是一个自动寻路的AI组件&#xff0c;我们首先来学习基本的使用。 画面中我已经添加好了地面&#xff0c;目标&#xff0c;障碍物以及玩家四个要素。 注意我们要…

vue的created和mounted区别

在Vue.js中&#xff0c;created和mounted的核心区别在于调用时机和DOM可访问性‌&#xff1a;created钩子在组件实例创建后、DOM挂载前调用&#xff0c;适用于数据初始化&#xff1b;mounted钩子在DOM挂载后调用&#xff0c;支持DOM操作。‌‌ ‌调用时机与核心能力对比‌ ‌…

MySQL 8.0 OCP 英文题库解析(十四)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题121~130 试题1…

【HarmonyOS 5】拍摄美化开发实践介绍以及详细案例

以下是 HarmonyOS 5 拍摄美化功能的简洁介绍&#xff0c;整合核心能力与技术亮点&#xff1a; 一、AI 影像创新 ‌AI 魔法移图‌ 系统级图像分层技术实现人物/物体自由拖拽、缩放与复制&#xff0c;突破传统构图限制。自动分离主体与背景&#xff0c;一键生成错位创意照&…