dip1040部分讨论
W.B
对上面
的回复:
@活动
就是这样分析的,只是我不知道其叫定点分析
.副作用
是较贵
,也许dip
应允许dfa(有限状态机)
可选.并初始时置
为最差
.
dfa
不是根据如/当/每一
来考虑.而是按边连接的节点流(一堆节点)
来思考的.即用至(goto)
语句来替换所有结构代码
.其实,就是将一堆代码
当作一个节点
.所有可降级
为结点
的块都可自然工作.AA和我
已经讨论最后使用
优化近10年
了.但由于需要dfa
,我总是避免
它.
通过嵌套
函数做dfa
是可以的,我准备那样搞@活
函数.dip
应该指明,把复制->移动
是依赖实现
的.如果用户依赖特定序列的移动/复制
,则是错的,就像优化命名值
依赖它
是错的一样,优化命名
是可选优化
,光看代码
很难判断是否有该优化
.现在主要是收集可行的命名值优化
.可将复制->移动
标记为依赖实现
的优化.
H.S.
只要解引用
指针时,把寄存器
写回栈
就行,如果不解引用
,则不必写.构
一直在寄存器
中.用移动构造带指向自身指针
实际上,根本不调用移动构造器
(无转义引用的本地变量).编译器
可推导
出无取构
地址,就完全放在寄存器
中.或代码
调用移动构造器
,但此后未读取
更新指针
,则优化器
可删掉整块
,仍把构
放在寄存器
中.
上面,与语言
指定的移动构造器
的语义
无关.对特定
构造,语言指定
特殊语义
,但对优化器
,只要整个语义
不变,则可任意
变形(转换).
M.H:
移动语言,管理生命期
,因而S
可能需要析构器
来避免放在寄存器
中.
deadalnix回复M.H.:
对c++
对的,但对d
目前不对.如果在abi级
强制按引用
而不按值
,则不是先行者
了.
W.B.
:
为什么这样限制?问题是决定
调用者何时为局部变量
分配内存.与优化命名
一样.
非emo
,但定义移动构造
?无移动赋值
,则无emo
语义,应用移动构造器
节内容.
按移动引用
传递实例,因为按引用
传递,则不调用
移动构造器.
有了emo
,则不必用引用
注解,如用引用
,则不应用特殊emo语义
.dip
的动机也有内部移动指针
的构的原因.
deadalnix
:
严格是为了健壮
性,按引用
传递时,所有者
仍是调用者
.函数
中也存在既想
按引用
返回,也想按值
取.
M.H
:
我是很喜欢隐式移动
的.它不是按移动
传递构
,而是由构
决定.当你在移动中考虑
对象生命期
时,不是按引用值
传递.值得认真考虑.
deadalnix
:
用赋值号
有问题,本
引用未初化
,而未完全消费
旧值.因而需要
构造器,不应让其他构作为参数,结论:把postblit
回收为移动构造器
.
W.B
:
赋值号
赋值已初化
对象,构造器
针对未初化
对象.
移动后销毁
是处理
源与目标都引用同一对象
.
先消灭
目标原内容,会在移入源
前,破坏源
.这是因为源/目标
一样的.这对交换/引用计数对象
都是一样的.理念是:由移动赋值
操作搞定
,使它看起来移动
了,然后析构
.从右值
构造本质也是移动构造
.新语义
仅针对EMO
对象,要求移动构造/赋值符
.移动构造器
是新符号
,因而不会破坏现有
代码.
deadalnix
:
是不是带析构器
的每次移动
都要求复制
?不,只是要先初化
该对象.移动
,不必造
一个要析构
的对象.
对W.B
:不,你需要消灭右值
.c++
移动构造器仍要求旧对象
有个空
状态,使析构
为空操作
.当有构造器
时,你创建新对象,你需要处理
旧对象.这也是我建议改后复制
为后移动
构造器的原因.
H.S.T
:好像未讨论常/不变
.
W.B
:右值没用了,为啥不简单
移动它?
常/不变
与后复制
有关,本提议不依赖后复制
.
deadalnix
:如果移动构造器
中有两个对象,但只能保留一个,则要析构
一个.常/不变
的后复制
,是由于后复制
不能区分移动/复制
.后复制
不适合复制
,但对移动
很好,且改动不大.
W.B
:后复制
把我们坑安逸
了.
移动构造
要点是移动
已初化对象至未构造
对象,不需要析构.而移动赋值
需要析构目标的原值
.
deadalnix
:后复制
对单
个对象管用.而不是好的复制
机制,因为复制
包含2
个对象.移动对单对象
管用.移赋号
同样会有后复制
的复制
问题,因为他同2
个对象工作,而移动
应只是单对象
.后复制
适合移动
,因其对单对象
管用.而不是让对象
有某种神奇状态.提议中就有许多奇技淫巧
.里面很多漏洞.如,如果不能移动构
的一个字段,则如何析构
该字段?如果有N
级深呢?最后又是空状态
,然后后移动
时析构对象.但又有优化问题.如果不这样,则该析构
的未析构
.
而后复制
的天性在开始时就禁止了.如果不能移动
某字段.则此处
必须为新值
.从而析构
旧值.
你不能确保需要析构的没有废物
,强制也不可能,至少也复杂.即你始终要调用析构器
,来确保有个空状态
.
移动赋值
的总理念是从c++
里面继承并修改的,这对d
没用.只要你有2
个对象,你就不是纯移动
.c++
将源对象置空
,然后析构
.你不能有2
个对象,却不析构
1个,然后期望没有问题.就像后复制
的复制
一样.你只是反向
有一样
的问题,即当你想要2个
对象的时候,你只有1个
对象.
W.B
,dip1040
仅针对构
.不对类
.
对tsbock
:你说得好,用户应该同时定义移动赋值/移动构造
.或者都不定义.只定义1个
应算错误.
移动构造
的理念是由用户
来移动
.即处理析构
.如,你有A
,B
,把B
移进A
,则必须析构A
.而之前的B
也不要了.而B
中内容现在在A
处.都是这样.
与类无关,alias this
很糟糕.我建议在类
中永远
不要用别名 本
(重要
).我想让其非法
.如果你的类中有带移动构造
的构
,且别名 本
至该构,则只能说,抱歉
了.
ts:我想知道通用移动赋值算法
.答案是,依赖程序员
如何设置对象内容
.特别是所有权
如何工作.由程序员
负责使其工作.如,如果
指向单独对象
,则先调用目标析构器
.如指向引用计数器
,则最后
调用析构器
.语言不指定
,由程序员
指定.
构/类
不同.dip
针对构
.
对dead...
:不能那样,你写自定义
析构器时,编译器也不能验证它
.移动构造/析构
都在同一个构,应同时开发.在管理内存时,一定程度上,要求程序员
知道自己在干什么.
后复制
的问题是不能访问两个对象
.opAssign
可以.两个参数
都可应用限定符
.所以能够工作.
移动赋值
也访问两个对象
,从而做正确的事
.
程序员可用赋值号
完全控制操作,包括何时及如何
析构原目标
内容.
对H.S.T
:是的
,别名 本
坑了我们.但移动赋值
是新事物,忽略其对类的别名本
不会破坏现有
代码.如果用移动构造
,不要同类
的别名本
混用.仅应在构
中用别名本
.
自动引用参数
仅针对模板函数
,左值时其
为引用参数
,否则是值
参数.
可能与本(S)
冲突,这是右值参
的符号,不过可用dip1040
替代.
期望结果是明显的:移动前两个对象
,移动后1个
对象.如果是2个为同一对象
,则无操作
.
H.S.T
:隐式转换,初期方便/快
,但后期可读/维护性
不好.因而,依赖别名 本
不好.
bachmeier
:如你调用
c库矩阵操作.你有多种
分配底层内存策略,可用别名 本
.否则,你代码很冗余
.此时,很有用.
jmh530
:泛型提供一个不用别名 本
的解决方法.许多通用库最后是调用BLAS/LAPACK
.
Alex
:元编程用来解决冗余代码
,你为何不用模板/串插件
?
deadalnix
:W.B程序员管理内存
.这很难做对.因而发明构造器/析构器
.如,除了运行代码,析构器析构构的所有字段
,你可以说开发者可手动析构
所有字段.当然也可以,但选择的设计
不能这样,因为会泄露内存
.
一种是可靠性
,在A
点稍微变了,在B
点造成意外
.则不可靠
.
加新成员
至构中,会像其他字段自动析构
,这是设计正确,可靠,可组合
.因为每个字段的析构都工作了.且只编程来确保交叉字段的不变量
为真.
如引用计数
的共针(c++概念)
.该针不知道如何析构
.T
知道.共针
只需保持RC
最新,且选择何时
析构.即它保持
引用计数和T
状态间的不变量
.如果无此类不变量
,则可让析构器
为空.
而现在
呢,描述的移动
是这样的:
1,挨个移动所有字段,
.2,如果需要维护不变量,则在结果上调用移动构造器
.
对我,2就是后复制
.但它与构造/析构
的原则不一致.会产生大量漏洞
.
如,要加一个字段
至构
.然后立即
移动赋值安静的无效了,更糟的是,如果字段
包含可析构
,则有严重错误,很可能在工作的构
外.如,新字段为灵针
,则安静的破坏
了灵针
提供的保证
.
现在决定,在移动构
中,在移动赋值
后消除所有字段
.但,我们又返回所有构必须
有空状态
,否则你不能让他们作为其他构
的字段
.
或破坏构造/析构
机制?构造/析构
就是来保证
程序的不变量
.因此,不行.
对,对复制
不适用.复制时,有2
个对象.
移动时,提供给对象的任何方案
,当最终仅存在1个
对象,的方案,将如同对复制一样的后复制
,产生大量漏洞
.这很明显.你有两个对象,却假装
只一个对象.与你有1个
对象,却假装
2个对象,都会产生各种问题
.赋值号
的全部控制.会打断构造/析构
机制.因为构
是可组合(作为其他构的成员)
的.然后混乱
就扩散开来了.
Timon:
import std.stdio;
struct T{
~this(){
writeln("调用T析构器");
}
}
struct S{
T t;
~this(){
writeln("调用S析构器");
//注意,未显式调用T的析构器.
}
}
void main(){
S s;
}
---
S destructor called
T destructor called
---
如果,你没显式
移动字段,则不调用析构器
.加新字段
,你不更新,析构器仍然表现正确.而这对移动构造器
不成立.
如果用移动
,则更新
字段时,会泄露
,因为没有调用析构器
.设计是易错
的,而后复制
没有这个问题,因为默认正确移动了未显式
指定字段.
我的建议是,在移动构造器(赋值号)
中初化所有字段,但只是减小
,而不是消除
风险.很可能这种错误很烦人.
deadalnix
:timon
.
很不错.主要问题是我们的要求很松
.没提供有用的保证
.
我们有个系统:构造器/析构器
.确保如果对象在正确状态,则程序可用该对象.对每个对象,有相应的析构器
来撤销
该状态.由编译器保证构造/析构
对.
将析构构造的.但复制对象
呢?则要构建
新对象,且也可析构
.由于复制/析构
很贵,我们想合并复制/析构
为移动
操作.因此,要求:
1
,对象构造/析构
要成对1:1
.
2
,对象(构造--析构)(生命期)
外不能使用.
3
,为了优化,当复制对象
后,不用时,我们想删构造/析构
对.
1,2
太贵.c++与d
显著区别是,c++
对象地址是固定
的,而d
不是.d
编译器可到处移动
对象.后果很严重.如例,c++
只1个解引用
.而d
有2个.哪里来的.
因为c++
的构,默认不可移动
,函数调用前存在的结构
,一定在调用者
的栈上.在abi
级,其按引用
传递,为了看起来
像按值
传递,在调用
后复制/析构
它.
实际上,其比在最简单
示例上更严重.引起跨程序大量解引用
,还只是表面
影响.如果没有额外间接
,都是很明显的.现在大量用指针
.使优化器必须
分析别名来证明它.很多情况下,还不能.
void foo(unique<int> a, unique<int> b) {
a = make_unique<int>(...);
//编译器,要假定可能已修改b,因为`a/b`可能指向相同对象
}
这是c++对象模型
的主要问题
.d
目前没有该问题.给了我们又一个要求.
4
,在abi
级上,对象必须按值
可用,包括带析构/构造
对象.
该问题是内部指针
(以后再讲).而大量
构中,不必用内部指针
,因此,应该丢掉它.不必支持
内部指针.
另一个c++
对象模型的怪异点
.可见,先分配/初化
灵针,然后按引用
传递至函数
.这没错.但调用函数后,搞笑的事来了.有个测试/分支
.生成的代码测试灵针值
,仅当指针非空
时释放内存
.为了好读,我让函数
不抛,但删
了不抛
后,生成大量异常处理
代码.
这是因为函数可能会移动
对象.尽管最好方法是不用
被移对象,但c++
为了后向兼容
不可能.因而给移动对象
加了个空
状态.其析构
操作为无操作
.该空
状态是大量析构器
工作之源,且生成大量无用代码
.
如果函数
移动对象,我们希望不析构
.如果要
析构,我们希望不检查空状态
.我们希望被调
来做,因为调用者
无相应信息.但由于先前
问题,是按引用
传递给被调
,这是个复杂任务
.
这就要求5
,对象不能有空状态
.当前的dip
,要实现3
,就破坏1
或5
要求.
当前的移动构造/赋值
从可用
中移动
.必然会发生以下之一:
a
,移动后,不管
先前对象.即构造器
不再默认确保1
要求,因为不析构
剩余.因为默认
操作都是错的,开发者更可能错
.可能安静
破坏1
.而且可外部
改变.改变成员的成员
即可安静
破坏1
.
b
,析构先前对象
,但我们要放空状态
,因为我们移动
了.这是c++
的办法,它破坏了5
要求.
该问题,基本无法避免
.因为我们有两个
对象,而语义
上,我们只有1个
对象.我们或者析构
剩余,破坏了5
要求.或者不管
剩余,则破坏了1
要求.
那我们该如何处理移动构造?
.难道不能直接
递归按字段
移动构
并处理它吗?
是的,对95%
用例,有问题,我分为两类:
1,不可移动构
.如在头
或更大数据段
,还有保证
按序构造/析构
构,可禁用移动构造器(不管内容)
来达到,这容易实现,只是需要存在移动构造器
.而已.
2,可移动构
,要求记账
,在没有内部指针
时,可用后复制
,
内部指针
即包含指向自身成员
的指针
的构.
内存慢
,计算快
,导致使用相对指针
.作为自己
的偏移.不过内部指针
很少见啦.只是这是死角
,要注意.因为不常见
例外,而丢掉好东西,不值得.
我怀疑,使对象不可移动
,然后手动
移动,可能也行.只是加个移动前地址()
.在后复制
后,返回前面对象
地址.这样就不要求4
了.在abi
级可经常按引用
传递.这样破坏限制
最少,其只影响性能
,而不影响1/5
的正确性.但也可能不值得.
对Q...
:
就是这个问题,容易假定事物
是必要的,但怀疑
他们可产生好点子
.d
的纯属性
就是.
有个主要问题,要求所有可移动对象
有个空状态
,或者要付出.不过构
有空状态
也许也好.
有点长,我们的假定集不一样.
可有多种设计,但都放弃了重要
属性,以致于不利
,这样,并无多少收获.
你期望程序员
来保证,但这是不够的,不然,何必检查边界
?或从头构造/析构
?只需要在所有路径
上调用析构
就完了.
问题是:假定破坏得,比构造/析构
更多,甚至破坏构造/析构
.
W.B
,编译器当前在构造器
中有些流分析
(来限制实现
).
dip1014
来记账
.标记构为不可移动
可解决.注册所有实例
限制了外部引用构的闭包
.
当前编译器
不移动构
,所以不存在内部指针
问题.
序号 | 可能 |
---|---|
1 | 在@安全 代码禁止移动构 ,或至少禁止移动含指针 构.但,引用计数 对象必然有指针 . |
2 | 赋值指针时,加运行时 检查 |
3 | 检查指针值时,加不变量 检查 |
4 | 要求指针字段 标记为@系统 , |
原先不允许有内部指针
的原因是可能会用
紧压垃集.
【推荐】国内首个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岁的心里话
· 按钮权限的设计及实现