admin 管理员组文章数量: 887053
dip1000,2
原文
上一篇文章显示了如何用新的DIP1000
规则让切片和指针
内存安全的引用
栈.但是D也可其他方式
引用栈.
面向对象实例
前面说过,如果了解DIP1000
如何同指针
工作,那么就会了解它如何同类
工作.示例:
@safe Object ifNull(return 域 Object a, return 域 Object b)
{return a? a: b;
}
中域与
如下相同:
@safe int* ifNull(return 域 int* a, return 域 int* b)
{return a? a: b;
}
原则
是:如果参数列表
中对象应用域/中域
存储类,则按参数
是实例指针
一样保护
对象实例地址
.从机器码
上看,它是实例指针
.
普通函数
,仅此而已.类或接口
成员函数呢?如下:
interface Talkative
{@safe const(char)[] saySomething() 域;
}
class Duck : Talkative
{char[8] favoriteWord;@safe const(char)[] saySomething() 域{import std.random : dice;// 如下不行.// return favoriteWord[];//禁止.1// 如下可以return favoriteWord[].dup;// 可返回完全不同的,// 头2/5返回第1项// 接着2/5返回第2项// 剩下1/5返回第3项return["quack!","Quack!!","QUAAACK!!!"][dice(2,2,1)];}
}
成员函数名
前后的域
,按域
标记this
,来防止本
从函数
中泄漏.因为保护
了实例地址,所以禁止
直接引用字段地址
逃逸.(.1
)是存储
在类实例中的静态数组
,返回切片
会直接引用它.而favoriteWord[].dup
返回不在类实例
中的数据
副本,因而可以.
或,可用中域(允许直接返回)
替换Talkative.saySomething
和Duck.saySomething
中的域
.
DIP1000
和里氏
替换原则
里氏
替换原则,总之,继承函数
比父函数
更严格.DIP1000
就是这样.规则如下:
1,如果父函数
中参数(包括隐式this
引用),没有DIP1000
属性,则子函数可指定自己为域
或中域
.
2,如果在父
中指定了域
参数,则必须在子
中同样指定域
.
3,如果在父
中指定了中域
参数,则同样必须在子
中指定域/中域
.
如果无属性,调用者
得不到保证;该函数
可能会存储参数
.如果有中域
,调用者可假设
除了返回值
外没有存储参数地址
.用域
,保证不存储
地址,这是更强大
的保证.示例:
class C1
{ double*[] incomeLog;@safe double* imposeTax(double* pIncome){incomeLog ~= pIncome;return new double(*pIncome * .15);}
}
class C2 : C1
{// 语言角度正确override @safe double* imposeTax(return scope double* pIncome){return pIncome;}
}
class C3 : C2
{// 正确override @safe double* imposeTax(scope double* pIncome){return new double(*pIncome * .18);}
}
class C4: C3
{//不行,C3.imposeTax是`域`,这放松了.override @safe double* imposeTax(double* pIncome){incomeLog ~= pIncome;return new double(*pIncome * .16);}
}
ref
特殊指针
讲了指针和数组,然后是在DIP1000
中使用structs
和union
.引用struct
或union
时,工作
方式与引用其他类型
时相同.但是在D中,指针和数组
并不是使用结构
的正规方式.它们一般按值传递,或在绑定到ref
参数时按引用传递.现在解释DIP1000
如何同ref
工作.
它们不像
指针那样.一旦理解ref
了,就可用DIP1000
完成其他
无法做到工作.
简单ref int
参数
最简单使用ref
方法可能如下:
@safe void fun(ref int arg) {arg = 5;
}
何意?ref
就像int*pArg
,在内部
是指针.但在源码中像值
一样使用.arg=5
等价于*pArg=5
.此外,客户好像参数
是按值传递
的一样调用
函数:
auto anArray = [1,2];
fun(anArray[1]); // 或UFCS: anArray[1].fun;
// 现在是[1, 5]
而不是用fun(&anArray[1])
.与C++
引用不同,D引用
可为无效(null)
,但如果null
不是用&
读取地址,则null
按段错误立即终止.
int* ptr = null;
fun(*ptr);
...
编译,但运行时
崩溃,因为给fun
内部赋值空地址
.
总是防止ref
变量地址逃逸
.
@safe void fun(ref int arg){arg = 5;}
//==
@safe void fun(scope int* pArg){*pArg = 5;}
因而,
@safe int* fun(ref int arg){return &arg;}
不会编译,因为
@safe int* fun(scope int* pArg){return pArg;}
也不会.
然而,有中引用
存储类,与中域
一样,禁止其他形式
逃逸,但允许返回参数地址
,
@safe int* fun(return ref int arg){return &arg;}
上面,是有效的.
引用引用
引用
,整
或类似类型,比指针更干净,但ref
引用引用时,更强大.如,引用
指针或类.可应用域
或中域
至引用的引用
,如:
@safe float[] mergeSort(ref return scope float[] arr)
{//中域保证.import std.algorithm: merge;import std.array : Appender;if(arr.length < 2) return arr;auto firstHalf = arr[0 .. $/2];auto secondHalf = arr[$/2 .. $];Appender!(float[]) output;output.reserve(arr.length);foreach(el;firstHalf.mergeSort.merge!floatLess(secondHalf.mergeSort)) output ~= el;arr = output[];return arr;
}
@safe bool floatLess(float a, float b)
{import std.math: isNaN;return a.isNaN? false:b.isNaN? true:a<b;
}
mergeSort
这里保证不会
泄露除了返回值
之外的floats
的地址.arr
与中域float[]arr
保证相同.但同时,因为arr
是ref
参数,mergeSort
可改变传递
给它的数组
.然后客户可写:
float[] values = [5, 1.5, 0, 19, 1.5, 1];
values.mergeSort;
用非ref
参数,客户就要values=values.sort
(虽然这里不用引用
是合理的).而指针
无法这样,因为中域float[]*arr
会保护数组元数据
地址(数组的length
和ptr
字段),而不是内容
地址.
也可给域引用
提供可返回的ref
参数,由于该示例有单元测试,在编译二进制
文件中,记住使用-unittest
编译标志.
@safe ref Exception nullify(return ref scope Exception obj)
{obj = null;return obj;
}
@safe unittest
{scope obj = new Exception("Error!");assert(obj.msg == "Error!");obj.nullify;assert(obj is null);// nullify按引用返回.可赋值给返回值obj.nullify = new Exception("Fail!");assert(obj.msg == "Fail!");
}
这里返回传递给nullify
参数地址,但仍保证其他通道
不会泄露对象指针
和类实例
的地址.
return
不强制返回遵守ref
或域
.
void* fun(ref scope return int*)
上面何意?规范指出,按引用 中
对待非中域
.因此,等价于:
void* fun(return ref scope int*)
但是,这仅适合有个ref
时.
void* fun(scope return int*)
//==
void* fun(return scope int*)
甚至:
void* fun(return int*)
//==
void* fun(中域 int*)
成员函数和ref
一般需要仔细考虑,ref
和return ref
来跟踪保护哪个地址
及可返回
.熟悉后,理解structs
和unions
如何同DIP1000
工作就非常简单了.
与类主要区别
在,this
引用只是类成员
函数中的普通类引用
,而this
而在构或联
成员函数中是ref StructOrUnionName
.
union Uni
{int asInt;char[4] asCharArr;//返回值包含联的引用,无论如何,不会逃逸引用 @safe char[] latterHalf() return{return asCharArr[2 .. $];}// 该参隐式引用,// 中值不引用该联,我们不会泄露它.@safe char[] latterHalfCopy(){return latterHalf.dup;}
}
注意,return ref
不应与this
参数一起使用.无法解析:
char[] latterHalf() return ref
语言必须理解
ref char[] latterHalf() return
意思:返回值是ref
引用.而"ref"
是多余的.
注意,在此没有使用域
键关字.就像域 引用 整
或域 整
参数是无意义的,因为它不包含引用
,因而域
在该联合中毫无意义
.域
仅对在别处
引用内存
类型才有意义.
域
在构/联
中等价于在静态数组
中.即成员引用
的内存
不会逃逸.示例:
struct CString
{// 需要用挂名成员把`指针`放在`匿名联`中,否则`@safe`用户代码可赋值`ptr`为不在C串中的字符.union{// D编译器`优化`空串字面为`空指针`,必须这样做才能使`.init`值真正指向`'\0'`.immutable(char)* ptr = &nullChar;size_t dummy;}//在构造器中,"`返回值`"是构造的`数据对象`.因此,此处返回`域`确保此结构不会比内存中的`arr`长.@trusted this(return scope string arr){
//注意:不要`正常断定`!可能会从`发布`版本中删除,但是该`断定`对`内存安全`来说是必要的,所以要用`assert(0)`来代替,永远不会删除它.if(arr[$-1] != '\0') assert(0, "非C string!");ptr = arr.ptr;}
//`返回值`引用`此结构`中成员相同内存,但不会通过`其他方式`泄漏`它的引用`,因此返回`域`.@trusted ref immutable(char) front() return scope{return *ptr;}//未传递数组指针引用.@trusted void popFront() scope{
//否则用户可能会跳出串末尾然后读它!if(empty) assert(0, "越界!");ptr++;}// 同样.@safe bool empty() scope{return front == '\0';}
}
immutable nullChar = '\0';
@safe unittest
{import std.array : staticArray;auto localStr = "你好啊!".staticArray;auto localCStr = localStr.CString;assert(localCStr.front == 'h');static immutable(char)* staticPtr;// 错误,逃逸本地引用// staticPtr = &localCStr.front();// 好.staticPtr = &CString("全局\0").front();localCStr.popFront;assert(localCStr.front == 'e');assert(!localCStr.empty);
}
第一部分说@trusted
对DIP1000
,是把可怕脚枪
.该示例说明了原因.使用普通断定
或完全忘记
它们是多么容易,或忽略
使用匿名联合
的需要.认为可安全使用该结构
,但完全忽略
了一些东西.
域
不仅用于注释参数和局部变量
.还可用于域类
和域保护
语句.已弃用域类
,而域保护
与DIP1000
或变量寿命控制
无关.
中/中引用/中域
还有个意思:
@safe void getFirstSpace
(ref scope string result,return scope string where
)
{//...
}
return
属性的一般
含义在此无意义,因为函数为void
返回类型.这里有个特殊
规则:如果返回
类型是void
,且第一个参数是ref/out
,则假定后续的中(引用/域)
通过赋值给第一个参数
来逃逸
.对结构
成员函数,假定赋值为结构自身
.
@safe unittest
{static string output;immutable(char)[8] input = "栈上";//试赋值给栈变量,不会编译getFirstSpace(output, input);
}
既然有了out
,对result
而言,out
比ref
好,out
与ref
差不多,唯一
区别是在函数
开始时自动默认初化引用
数据,表明out
参数所引用数据
保证不会影响
函数.
编译器用域
在函数体内优化类分配
.如果用new
类,初化域
变量,编译器
把它放在栈中.示例:
class C{int a, b, c;}
@safe @nogc unittest
{// 单元测试为@nogc,// 如无域优化,则不编译scope C c = new C();
}
需要显式使用域
关键字.推导域
不管用,因为这样初化类一般不会(无@nogc
属性)强制限制c
.该功能目前仅适合类
,但也可与新建的
结构指针和数组字面一起使用.
这,基本上就是dip1000
的手册了,
下节
讲推导属性
,也与dip1000
有关.还会涵盖一些敢于使用@trusted
和@system
编码时的注意事项
.存在危险
系统编程需求
,D
也可减小风险.
版权声明:本文标题:dip1000,2 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/free/1699405267h347422.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论