d结构的移动语义
勾挂D的结构移动语义
概述
当前语言定义禁止结构类型维护到其实例
的外部/内部
引用,因为D可能选择通过简单的位复制
操作来移走结构实例
.
此DIP的目的是保留此函数,并也允许到实例的内部/外部引用
.这是通过允许该结构定义类似后复制
的回调(叫后移动操作
)来实现的,在移动后调用它,从而允许该结构更新移动
搞失效的引用.
参考
#17448问题:由于缺乏这种支持而引起的问题,以及有关为什么需要这种支持的讨论.
C++解决同一问题的方法
理由
允许D编译器移动(而不是破坏)已到达域尾
的栈分配
的结构对象.尽管这很有用,但表明编程模式更难.
通常表述该限制
为"D结构可能不含指向自身
的指针".尽管该限制是正确的,但它不是唯一.例如,D结构也可能不在构造/析构
函数向跟踪系统中的所有实例
的全局注册表注册自己,即通过链式列表
.这也严重限制了从结构外部存储引用结构实例的闭包
的能力.
尽管并非所有这些情况都可以通过此DIP轻松解决,但如果没有它,程序员将没法
解决该问题,即使她很幸运的提前发现内存崩溃问题.
描述
大概
DIP建议修改:
在DRuntime
中加一个*__后移动的函数
用户可在构中定义后移动操作
成员函数.如果定义,该函数必须遵循良好定义的接口.
决定移动结构实例
时,编译器必须在移动实例和释放包含旧实例内存间
调用__后移动*. __后移动必须
接收移动前后
实例的引用.
__后移动的实现
应按与以下代码兼容的方式定义*__后移动*:
空 __后移动(S)(ref S 新位置,ref S 旧位置)不抛
如(是(S ==构)){
每一(成员名; __特征(allMembers,S)){
静态 如(是(typeof运算(__特征(getMember,S ,成员名))==构)){
插件(" __后移动(新位置. " ?成员名? "旧位置. " ?成员名?");");
}
}
静态 如(__特征(hasMember,S,"后移动操作")){
新位置.后移动操作(旧位置);
}
}
注意,S也可为shared,immutable或const
.
后移动操作
后移动操作
(如果已定义),则必须是在复制外部/内部
引用后更新它们的不抛
函数.实施者应使其@nogc与@safe或@trusted
.
实现者可定义后移动操作
的常和/或不变
版本.如果是这样,实现代码可安全地在对象目标位置修改数据,因为无指向对象的指针.需要转换
此类修改.
修改按指针引用的
结构中存储的外部数据
或在移动中的源地址的数据
是否安全(如侵入式链表),在很大程度上取决于这些指针的精确语义细节
.后移动操作
的用户文档必须
说明什么是安全及不安全
的.
实现者也可选择不定义常/不变
版本的后移动操作
.如果编译器尝试移动此类结构的实例,将导致编译时错误.
后移动操作
的实现文档还必须强调,虽然允许在后移动操作
源位置处操作内存,但函数返回后,将无害的释放内存.应鼓励实现者按常 引用
定义后移动操作
的源参数,以获得编译器防止意外操作
的保护.这不会影响实施者访问数据,因为她已在目标位置拥有副本了.
编译器移动时发出的代码
移动结构的实例时,如果提供新旧实例
的地址,编译器必须调用*__后移动*.
后移动操作
装饰注意事项
后移动操作
应是@nogc和@safe或@trusted
.如没有,则编译从内部上下文是@nogc或@safe的结构实例
移动的代码,会导致编译错误.后移动操作
,如实施,必须为不抛
.
可通过按@不抛 @nogc @safe
修饰*__后移动自身来强制使用这些属性,从而避免按其他任何方式定义后移动操作
.不建议这样做,比如用户可能从不用@safe
.
强迫她在电脑上使用后移动操作
是没有意义的.
由于模板函数的属性推导,如果后移动操作
所有实例成员都是,如@nogc
,d会自动对该结构按@nogc
定义__后移动*
提议确实要求在后移动操作
上不抛
,抛出就意味着程序流的变化.
示例
为了方便讨论,以下是需要后移动操作
来保持正确性的具体示例.
内部引用
考虑跟踪数字的结构.这可是局部(每个结构)编号或全局编号.一种可能实现是:
struct Tracker {
static uint globalCounter;
uint localCounter;
bool isLocal;
@禁用 this(this);
this(bool local){
isLocal =local;
localCounter = 0 ;
}
空 增量(){
如(isLocal)
localCounter++;
否则
globalCounter++;
}
}
使用如子句
来确定更新谁会使性能很差.除非成功预测分支,否则在现代CPU上分支是昂贵操作
.
如果该结构的实例在全局和本地更新间均匀分布,则分支预测率为50%(白预测了),此实现性能将非常差.
让isLocal是个模板参数
能解决分支预测问题,但只能在编译时就能决定本地与全局
时才行.
性能更好的方法是使用指针
:
struct Tracker{
static uint globalCounter;
uint localCounter;
uint *计数器;
@禁用 this(this);
这个(bool local){
localCounter = 0 ;
如(本地)
计数器=&localCounter;
其他
计数器=&globalCounter;
}
空 增量(){
(*计数器)++ ;
}
void 后移动操作(const ref Tracker 旧位置){
如(计数器 是 &旧位置.localCounter)
计数器=&localCounter;
}
}
性能考量
对于未定义及成员未定义后移动操作
的结构,将导致对其成员实例递归调用空函数实现.编译器应能够通过内联消除这一系列调用.因此,对于不使用此函数的结构,此函数运行时成本为零.
如果编译器实现者担心内联不会在可应用时消除这些调用,则可手动消除无操作子树.可在*__后移动*最前
加:
静如(!hasElaborateMove!S)中;
在内部成员有后移动操作
定义情况下,这种附加会产生些编译时开销,因为在递归下降时多次扫描子树.但,无论编译器优化能力,都保证了零运行时间成本.
定义后移动操作
的结构管理自己的成本.
对d标库的影响
该建议对d标库影响很小.即使后移动操作
是为结构定义的,上面详细介绍的编译器处理也可确保对d标库影响不大.
例外是:
- 在std.algorithm中定义的
move族
函数要加上*__后移动*. - 要更新依赖精度实现
swap
函数. std.特征
中要加新模板hasElaborateMove
.如果结构或其任何成员定义了后移动操作
,则必须返回真
.
重大变更和弃用
提案本身没有引入重大更改,因为结构类型必须明确选进
此更改才能看到变化.
该提案确实加了两个具有特殊含义的函数.其中一个在保留空间中,因此不会破坏任何东西.除非已定义一个后移动操作
,则切换到支持此DIP的实现将破坏旧代码,没法.
由于D程序员通常都知道op*函数是针对运算符重载的,因此问题应该不常见.
讨论区
有人说,由于D采取了按值传递非POD结构
作为函数参数的方法,该提案可能需要更改D ABI
.DIP作者表示同意.
一种批评是,建议的函数破坏了@安全
.DIP作者说,唯一不安全是移动常/不变的构
,而DIP已提倡去掉常
.此时,需要@系统或@信任
.
最终审查讨论区
审查本dip时,在开发"复制构造器"的相关DIP.有人担心冲突.DIP作者说,与DConf的语言维护人员交流时确认无冲突.
一位审阅者问,如果程序员在类中定义后移动操作
函数会怎么样,DIP的作者说,今天应忽略它,以后不确定.
一个反对要求后移动操作
是不抛
的,理由是薄弱的"可能混淆".DIP作者建议可将要求放宽为"很好的建议",因为D的移动"无处不在",要阻止它这样做是"不可能的".
有人指出允许重载后移动操作
,顺带引出用@禁止注解后,该干啥
的问题,共识是,如这样,移动对象会导致编译错误.
有人提出在类
上后移动操作
问题.DIP作者最终建议应按类似当前对类
的opAssign
类似方式处理.
有人反对DIP提出方式,即指定编译器应该做什么及该如何表现,例如"编译器必须调用".DIP作者暗示愿意放弃这种术语,而指定确切结果,即编译器应表现如此.
有人建议DIP应指定复合结构
的调用顺序.DIP作者回答说,该提案仅要求(成员的后移动操作
必须在实例自身前面调用).
正式评估
语言维护者同意"这不是我们想要的DIP,而是我们需要的DIP",并说出现了许多问题,证明了这种需求.例如,他们引用了最近(在撰写本文时)C++的std::string
中使用内部指针
的发现.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现