D opEquals() 和 opCast() 给我一个分段错误
D opEquals() and opCast() give me a Segmentation Fault
我正在尝试用 D 编程语言编写表示全球贸易识别号 (GTIN) 的类型库。有四种 GTIN,每种都以数字本身的长度命名:GTIN8
、GTIN12
、GTIN13
和 GTIN14
。其中每一个都被建模为 class 继承自抽象 GTIN
class.
在摘要 GTIN
class 中,我覆盖了 opEquals 运算符来比较任何类型的两个 GTIN
。由于所有较小的 GTIN
都可以映射到更宽的 GTIN14
,因此比较中的每个 GTIN
首先转换为 GTIN14
,然后再进行比较。
在我比较一个或多个 GTIN13
之前一切正常(或者可能是较小的 GTIN
类型),在这种情况下,我会遇到分段错误。
一切都从这个单元测试开始:
GTIN13 gtin1 = new GTIN13("123456789012");
GTIN13 gtin2 = new GTIN13("123456789012");
assert(gtin1 == gtin2);
运算符 opEquals()
的签名为 Object.opEquals(Object o)
,将签名更改为其他任何内容都不会覆盖 ==
。在我的例子中,这变成了对 gtin1.opEquals(cast(Object) gtin2)
的调用。我的 opEquals
覆盖看起来像这样(减去累积的注释掉的代码和调试语句):
public override @trusted
bool opEquals(Object other)
{
GTIN14 a = cast(GTIN14) this;
GTIN14 b = cast(GTIN14) other;
for (int i; i < a.digits.length; i++)
{
if (a.digits[i] != b.digits[i]) return false;
}
return true;
}
如您所见,每个 GTIN
在函数的开头都被转换为 GTIN14
;但是,other
首先转换为 Object
.
public
GTIN14 opCast(GTIN14)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < 13)
currentGTINString = ('0' ~ currentGTINString);
return new GTIN14(currentGTINString);
}
另外,朋友writeln()
告诉我,GTIN14 b = cast(GTIN14) other;
行执行后,b
就是null
。 (该行之前不为空。)
所以,总而言之,问题似乎是将 GTIN14
以外的任何类型的 GTIN
转换为 Object
,然后以某种方式返回 GTIN14
完全删除对象。这是一个错误吗?这是我的代码有问题吗?有没有不影响代码质量的解决方法?
如果能得到任何帮助,我将不胜感激。
好的,我有几条来自你的 pastebin 的评论。
public abstract
class GlobalTradeItemNumber
{
// this could prolly just return _digits.length too.
public abstract @property
size_t length();
我在这里的一般评论是,您的代码可以通过从数组中借用长度来稍微简化....或者您也可以使用长度作为参数来做类似模板化 class 的事情.
不过,我稍后会回过头来,首先,让我们修复您的代码。继续 class:
public
/*
Note #1:
OK, this line is wrong:
//GTIN14 opCast(GTIN14)()
It should probably be:
*/
GTIN14 opCast(T : GTIN14)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < 13)
currentGTINString = ('0' ~ currentGTINString);
return new GTIN14(currentGTINString);
}
// we also need one for converting to the other sizes
// this could also be done with metaprogramming
GTIN13 opCast(T : GTIN13)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < 12)
currentGTINString = ('0' ~ currentGTINString);
return new GTIN13(currentGTINString);
}
该行错误的原因是它实际上定义了一个名为 GTIN14
的本地模板参数,它可以是 anything,并隐藏了外部名称!
因此,我在这里的更改创建了一个新名称 T
,然后专门用于仅匹配来自外部的 GTIN14 名称。参见:http://dlang.org/spec/template.html#parameters_specialization
但是,它只适用于 GTIN14,所以我还添加了第二个函数,它也适用于 GTIN13。
如果您使用的是模板化 class,专业化也可以提取长度,您将拥有一个函数来完成所有这些。或者,如果长度作为每个子项中的编译时常量(枚举)class,您也可以从那里提取它。
无论如何,只需使用专业化语法 (T : class_name
) 并为其他子classes 添加函数即可修复当前代码。
public
ulong toNumber()
{
ulong result;
// a note here: I would probably just have
// int exponent = 1;
// then exponent *= 10; in each loop instead of pow each time.
for (size_t i = this.length-1; i > 0; i--)
{
result += (this._digits[i] * (10^^(this._digits.length-i)));
}
return result;
}
好吧,没什么可说的,你的代码有效,我只是写得有点不同。累加器变量模式会稍微优化它。当然,这里不是很重要,只是要记住一些。
/*
Note #2: this is where things get broken
*/
public override @trusted
bool opEquals(Object other)
{
/*
these are actually two different kinds of casts
GTIN14 b = cast(GTIN14) other; // does a generic dynamic cast!
GTIN14 a = cast(GTIN14) this; // actually calls the opCast
*/
GTIN obj = cast(GTIN) other;
if(obj is null) // which might return null because Object is not necessarily an instance of your class
return false; // definitely not a match
GTIN14 b = cast(GTIN14) obj;
GTIN14 a = cast(GTIN14) this;
for (int i; i < a.digits.length; i++)
{
if (a.digits[i] != b.digits[i]) return false;
}
return true;
}
public override @trusted
int opCmp(Object other)
{
// GTIN14 that = cast(GTIN14) other; // a generic dynamic cast!
GTIN obj = cast(GTIN) other;
if(obj is null) // which might return null because Object is not necessarily an instance of your class
return -1; // so return something indicating not a match
GTIN14 that = cast(GTIN14) obj; // now you can use your custom cast
const ulong thisNumber = this.toNumber();
const ulong thatNumber = that.toNumber();
if (thisNumber == thatNumber) return 0;
return ((thisNumber / 10u) > (thatNumber / 10u) ? 1 : -1);
}
子class中的其他opCmp
犯了同样的错误,可以用同样的方法修复。
但这是造成您麻烦的主要原因 - 相同的 cast
语法实际上做了两件不同的事情!
看,other
的静态类型是泛型基础 class Object
,因此它还不知道您的自定义转换函数。 (opCast
,作为模板,不能是虚拟的,因此不像其他修改泛型函数行为的函数那样 override
)
相反,它使用通用的 dynamic_cast
(这是 C++ 名称,D 只是将它们全部称为 cast
,但它是相同的概念,因此您可以通过搜索C++ 术语,如果你愿意的话)。它尝试使用运行时类型标记将基 class/interface 转换回子 class。如果它确实是那个 class 的一个实例(或者它自己的子 classes 之一),那么转换成功并且你得到了引用。否则,它 returns null
。这是您的段错误的原因。
另一方面,cast(xxx) this
已经知道它是 GTIN
(或其子classes 之一)的实例,因此它能够使用您的自定义opCast
转换。因此,您的 a
变量调用了正确的函数并正确填充,但您的 b
变量将是 null
... 除非您实际上正在比较两个 GTIN14
实例.然后动态转换会成功,但其他 classes.
因此,解决方法是首先将通用 Object other
转换回您的 GTIN
基础 class,检查 null
(如果用户写像 GTIN14 a = new GTIN14("xxx"); Object b = new Object(); assert(a == b); /* uh oh, b is an Object, so it should just return null */
.
顺便说一句,在与 null 进行比较时,通常应该使用 a is null
而不是 a == b
,因为如果 a 本身为 null,则在尝试访问虚拟 opEquals
函数时会崩溃!
无论如何,在将其转换回 GTIN
之后,您可以再次转换 并调用转换函数。
或者,您也可以在基 class 中使用不同的命名函数,例如 toGTIN14
,它通常会进行转换,您只需从基 class 的每个实例中调用它=] 并以这种方式转换它们,而不是使用 cast
关键字。这实际上是我写的方式 - 这是我的偏好,但两种方式都有效。
opEquals
和 opCmp
,在实现它们的任何 classes 中,都需要遵循相同的模式。在 opEquals
中你可以看到 I return false
当它为 null 时,因为如果它们甚至不能转换为普通类型,它们显然是不相等的!
但在 opCmp
中,你不想 return 0 因为这意味着相等,但实际上 return 对我来说是个谜....我只是did -1 这样该数组中的所有其他对象都会更早排序,但也许您有更好的主意。我不知道什么是最好的。
无论如何,进行这些更改应该可以修复您的代码。
最后,作为奖励,这里是通用模板化的实现 class:
alias GTIN14 = GlobalTradeItemNumberImpl!14;
alias GTIN13 = GlobalTradeItemNumberImpl!13;
public
class GlobalTradeItemNumberImpl(int size) : GlobalTradeItemNumber
{
public override @property
size_t length()
{
return size;
}
this(string digits)
{
super(digits);
}
}
如果您曾经看过 Phobos 的一些内部结构,您会在 std.base64
和 std.digest
.
中看到类似的模式
现在,所有功能实际上都在基础 class 中。您可以在基础 class:
中重写 opCast
T opCast(T : GlobalTradeItemNumberImpl!N, int N)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < (N-1))
currentGTINString = ('0' ~ currentGTINString);
return new T(currentGTINString);
}
那里的专业化使用"pattern matching" 形式 #7 中描述的:http://dlang.org/spec/expression.html#IsExpression 来捕获任何随机 N,并提取它在函数内部使用的内容。
如果您有兴趣,我们还可以进行其他优化,例如使用 ~
运算符,或者甚至可以将其从 classes 更改为 struct
使用alias this
结合常用功能..但如果你愿意,我会让你玩:)
我正在尝试用 D 编程语言编写表示全球贸易识别号 (GTIN) 的类型库。有四种 GTIN,每种都以数字本身的长度命名:GTIN8
、GTIN12
、GTIN13
和 GTIN14
。其中每一个都被建模为 class 继承自抽象 GTIN
class.
在摘要 GTIN
class 中,我覆盖了 opEquals 运算符来比较任何类型的两个 GTIN
。由于所有较小的 GTIN
都可以映射到更宽的 GTIN14
,因此比较中的每个 GTIN
首先转换为 GTIN14
,然后再进行比较。
在我比较一个或多个 GTIN13
之前一切正常(或者可能是较小的 GTIN
类型),在这种情况下,我会遇到分段错误。
一切都从这个单元测试开始:
GTIN13 gtin1 = new GTIN13("123456789012");
GTIN13 gtin2 = new GTIN13("123456789012");
assert(gtin1 == gtin2);
运算符 opEquals()
的签名为 Object.opEquals(Object o)
,将签名更改为其他任何内容都不会覆盖 ==
。在我的例子中,这变成了对 gtin1.opEquals(cast(Object) gtin2)
的调用。我的 opEquals
覆盖看起来像这样(减去累积的注释掉的代码和调试语句):
public override @trusted
bool opEquals(Object other)
{
GTIN14 a = cast(GTIN14) this;
GTIN14 b = cast(GTIN14) other;
for (int i; i < a.digits.length; i++)
{
if (a.digits[i] != b.digits[i]) return false;
}
return true;
}
如您所见,每个 GTIN
在函数的开头都被转换为 GTIN14
;但是,other
首先转换为 Object
.
public
GTIN14 opCast(GTIN14)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < 13)
currentGTINString = ('0' ~ currentGTINString);
return new GTIN14(currentGTINString);
}
另外,朋友writeln()
告诉我,GTIN14 b = cast(GTIN14) other;
行执行后,b
就是null
。 (该行之前不为空。)
所以,总而言之,问题似乎是将 GTIN14
以外的任何类型的 GTIN
转换为 Object
,然后以某种方式返回 GTIN14
完全删除对象。这是一个错误吗?这是我的代码有问题吗?有没有不影响代码质量的解决方法?
如果能得到任何帮助,我将不胜感激。
好的,我有几条来自你的 pastebin 的评论。
public abstract
class GlobalTradeItemNumber
{
// this could prolly just return _digits.length too.
public abstract @property
size_t length();
我在这里的一般评论是,您的代码可以通过从数组中借用长度来稍微简化....或者您也可以使用长度作为参数来做类似模板化 class 的事情.
不过,我稍后会回过头来,首先,让我们修复您的代码。继续 class:
public
/*
Note #1:
OK, this line is wrong:
//GTIN14 opCast(GTIN14)()
It should probably be:
*/
GTIN14 opCast(T : GTIN14)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < 13)
currentGTINString = ('0' ~ currentGTINString);
return new GTIN14(currentGTINString);
}
// we also need one for converting to the other sizes
// this could also be done with metaprogramming
GTIN13 opCast(T : GTIN13)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < 12)
currentGTINString = ('0' ~ currentGTINString);
return new GTIN13(currentGTINString);
}
该行错误的原因是它实际上定义了一个名为 GTIN14
的本地模板参数,它可以是 anything,并隐藏了外部名称!
因此,我在这里的更改创建了一个新名称 T
,然后专门用于仅匹配来自外部的 GTIN14 名称。参见:http://dlang.org/spec/template.html#parameters_specialization
但是,它只适用于 GTIN14,所以我还添加了第二个函数,它也适用于 GTIN13。
如果您使用的是模板化 class,专业化也可以提取长度,您将拥有一个函数来完成所有这些。或者,如果长度作为每个子项中的编译时常量(枚举)class,您也可以从那里提取它。
无论如何,只需使用专业化语法 (T : class_name
) 并为其他子classes 添加函数即可修复当前代码。
public
ulong toNumber()
{
ulong result;
// a note here: I would probably just have
// int exponent = 1;
// then exponent *= 10; in each loop instead of pow each time.
for (size_t i = this.length-1; i > 0; i--)
{
result += (this._digits[i] * (10^^(this._digits.length-i)));
}
return result;
}
好吧,没什么可说的,你的代码有效,我只是写得有点不同。累加器变量模式会稍微优化它。当然,这里不是很重要,只是要记住一些。
/*
Note #2: this is where things get broken
*/
public override @trusted
bool opEquals(Object other)
{
/*
these are actually two different kinds of casts
GTIN14 b = cast(GTIN14) other; // does a generic dynamic cast!
GTIN14 a = cast(GTIN14) this; // actually calls the opCast
*/
GTIN obj = cast(GTIN) other;
if(obj is null) // which might return null because Object is not necessarily an instance of your class
return false; // definitely not a match
GTIN14 b = cast(GTIN14) obj;
GTIN14 a = cast(GTIN14) this;
for (int i; i < a.digits.length; i++)
{
if (a.digits[i] != b.digits[i]) return false;
}
return true;
}
public override @trusted
int opCmp(Object other)
{
// GTIN14 that = cast(GTIN14) other; // a generic dynamic cast!
GTIN obj = cast(GTIN) other;
if(obj is null) // which might return null because Object is not necessarily an instance of your class
return -1; // so return something indicating not a match
GTIN14 that = cast(GTIN14) obj; // now you can use your custom cast
const ulong thisNumber = this.toNumber();
const ulong thatNumber = that.toNumber();
if (thisNumber == thatNumber) return 0;
return ((thisNumber / 10u) > (thatNumber / 10u) ? 1 : -1);
}
子class中的其他opCmp
犯了同样的错误,可以用同样的方法修复。
但这是造成您麻烦的主要原因 - 相同的 cast
语法实际上做了两件不同的事情!
看,other
的静态类型是泛型基础 class Object
,因此它还不知道您的自定义转换函数。 (opCast
,作为模板,不能是虚拟的,因此不像其他修改泛型函数行为的函数那样 override
)
相反,它使用通用的 dynamic_cast
(这是 C++ 名称,D 只是将它们全部称为 cast
,但它是相同的概念,因此您可以通过搜索C++ 术语,如果你愿意的话)。它尝试使用运行时类型标记将基 class/interface 转换回子 class。如果它确实是那个 class 的一个实例(或者它自己的子 classes 之一),那么转换成功并且你得到了引用。否则,它 returns null
。这是您的段错误的原因。
另一方面,cast(xxx) this
已经知道它是 GTIN
(或其子classes 之一)的实例,因此它能够使用您的自定义opCast
转换。因此,您的 a
变量调用了正确的函数并正确填充,但您的 b
变量将是 null
... 除非您实际上正在比较两个 GTIN14
实例.然后动态转换会成功,但其他 classes.
因此,解决方法是首先将通用 Object other
转换回您的 GTIN
基础 class,检查 null
(如果用户写像 GTIN14 a = new GTIN14("xxx"); Object b = new Object(); assert(a == b); /* uh oh, b is an Object, so it should just return null */
.
顺便说一句,在与 null 进行比较时,通常应该使用 a is null
而不是 a == b
,因为如果 a 本身为 null,则在尝试访问虚拟 opEquals
函数时会崩溃!
无论如何,在将其转换回 GTIN
之后,您可以再次转换 并调用转换函数。
或者,您也可以在基 class 中使用不同的命名函数,例如 toGTIN14
,它通常会进行转换,您只需从基 class 的每个实例中调用它=] 并以这种方式转换它们,而不是使用 cast
关键字。这实际上是我写的方式 - 这是我的偏好,但两种方式都有效。
opEquals
和 opCmp
,在实现它们的任何 classes 中,都需要遵循相同的模式。在 opEquals
中你可以看到 I return false
当它为 null 时,因为如果它们甚至不能转换为普通类型,它们显然是不相等的!
但在 opCmp
中,你不想 return 0 因为这意味着相等,但实际上 return 对我来说是个谜....我只是did -1 这样该数组中的所有其他对象都会更早排序,但也许您有更好的主意。我不知道什么是最好的。
无论如何,进行这些更改应该可以修复您的代码。
最后,作为奖励,这里是通用模板化的实现 class:
alias GTIN14 = GlobalTradeItemNumberImpl!14;
alias GTIN13 = GlobalTradeItemNumberImpl!13;
public
class GlobalTradeItemNumberImpl(int size) : GlobalTradeItemNumber
{
public override @property
size_t length()
{
return size;
}
this(string digits)
{
super(digits);
}
}
如果您曾经看过 Phobos 的一些内部结构,您会在 std.base64
和 std.digest
.
现在,所有功能实际上都在基础 class 中。您可以在基础 class:
中重写opCast
T opCast(T : GlobalTradeItemNumberImpl!N, int N)()
{
string currentGTINString = this.toString()[0 .. $-1];
while (currentGTINString.length < (N-1))
currentGTINString = ('0' ~ currentGTINString);
return new T(currentGTINString);
}
那里的专业化使用"pattern matching" 形式 #7 中描述的:http://dlang.org/spec/expression.html#IsExpression 来捕获任何随机 N,并提取它在函数内部使用的内容。
如果您有兴趣,我们还可以进行其他优化,例如使用 ~
运算符,或者甚至可以将其从 classes 更改为 struct
使用alias this
结合常用功能..但如果你愿意,我会让你玩:)