【问题标题】:Creating strings in D without allocating memory?在 D 中创建字符串而不分配内存?
【发布时间】:2011-10-01 10:39:04
【问题描述】:

是否有任何类型安全的方法可以在 D 中创建 string,使用仅在运行时可用的信息,而不分配内存?

我可能想做的一个简单例子:

void renderText(string text) { ... }

void renderScore(int score)
{
    char[16] text;
    int n = sprintf(text.ptr, "Score: %d", score);
    renderText(text[0..n]); // ERROR
}

使用这个,你会得到一个错误,因为text 的切片不是不可变的,因此不是string(即immutable(char)[]

我只能想到三种解决方法:

  1. 将切片转换为string。它有效,但很丑。
  2. 使用切片分配一个新字符串。这可行,但我宁愿不必分配内存。
  3. renderText 更改为const(char)[]。这在这里有效,但是 (a) 它很难看,并且 (b) Phobos 中的许多功能需要string,所以如果我想以相同的方式使用它们,那么这不起作用。

这些都不是特别好的。我错过了什么吗?其他人如何解决这个问题?

【问题讨论】:

  • 异端邪说!使用snprintf! :D
  • 我认为#3 实际上是正确的解决方案。 string--> immutable(char)[] 表示数据不能更改(只要它仍然在某处引用,否则允许 GC 收集它,AFAIK)。我认为您永远无法为堆栈分配的数据做出这样的保证。但除非 renderText 确实需要在某处存储文本,否则它应该使用 const(char)[]。由于 immutable 比 const 更强大,因此它只应在必要时使用。我同意,但是 phobos 中的许多函数不必要地使用字符串而不是 const(char)[],这应该在 phobos 中修复。

标签: string memory-management immutability d


【解决方案1】:

您有 char 的静态数组。你想将它传递给一个接受immutable(char)[] 的函数。没有任何分配的唯一方法是强制转换。想想看。你想要的是一种类型表现得像另一种类型。这就是铸造的作用。您可以选择使用assumeUnique 来执行此操作,因为这正是您正在寻找的演员阵容,但这是否真的能为您带来任何好处是值得商榷的。它的主要目的是记录你通过演员所做的事情是使被转换的值被视为immutable,并且没有其他对它的引用。查看您的示例,这基本上是正确的,因为它是函数中的最后一件事,但是您是否要这样做通常取决于您。鉴于它是一个静态数组,如果你搞砸了它会冒内存问题,并且你将它传递给一个允许对它的引用泄漏的函数,我不确定assumeUnique 是最好的选择。但同样,这取决于您。

无论如何,如果您正在进行强制转换(无论是显式转换还是使用 assumeUnique),您都需要确定您传递给它的函数不会泄漏对您的数据的引用重新传递给它。如果是这样,那你就是在自找麻烦。

当然,另一种解决方案是更改函数以使其采用const(char)[],但这仍然存在泄漏对您传入的数据的引用的风险。因此,您仍然需要确定该函数实际上将要做什么。如果它是pure,不返回const(char)[](或任何可能包含const(char)[]),并且它不可能通过函数的任何其他参数泄漏,那么你是安全的,但如果有的话这些都不是真的,那么你将不得不小心。所以,归根结底,我相信所有使用const(char)[] 而不是转换为string 的做法真正为你买的是你不必转换。这还是更好,因为它避免了搞砸演员表的风险(一般情况下最好避免演员表),但在转义引用方面,您仍然需要担心所有相同的事情。

当然,这也要求您能够更改函数以获得所需的签名。如果你不能做到这一点,那么你将不得不投。我相信,在这一点上,大多数 Phobos 的基于字符串的函数已被更改,因此它们被模板化为字符串类型。所以,现在对于 Phobos 来说,这应该比以前少了。一些函数(尤其是 std.file 中的函数)仍然需要模板化,但最终,Phobos 中需要 string 的函数应该是相当少见的,并且有充分的理由需要它。

然而,最终问题在于,您试图将静态数组视为动态数组,虽然 D 绝对允许您这样做,但这样做是有一定风险的,您需要确保您使用的函数不会泄露对您传递给它们的本地数据的任何引用。

【讨论】:

  • 如何让 renderText 采用“范围 const(char)[]”? “作用域存储类意味着参数中的引用不能被转义(例如分配给全局变量)。”
  • @jpf:你确定吗? DMD 允许。
  • 这是来自 D2 语言参考的引用:digitalmars.com/d/2.0/function.html Section "Function Parameters"。不过,我在 TDPL 中找不到类似的东西。但如果它还没有正确实施,我不会感到惊讶;-)
【解决方案2】:

从 std.exception 中查看 assumeUnique 乔纳森的回答。

【讨论】:

  • 这将如何与带有 ref 参数的假设唯一交互(在本例中为 IIRC 不是 r 值的切片表达式)?
  • 我不认为使用带有堆栈分配空间的assumeUnique 是有效的。堆栈空间不是唯一的,当 renderScore 返回并调用另一个函数时,它可以重用堆栈上的相同空间。 --> 不是唯一的,也不是不可变的。请记住,不可变也隐含地意味着共享:您真的希望代码能够将堆栈分配的数据传递给另一个线程吗?
  • 你必须这样做:auto text2 = text[0..n]; renderText(assumeUnique(text2)); 尽管正如 jpf 所说,这很讨厌,因为你仍然对text 具有写访问权限,这违背了immutable 的目的。
【解决方案3】:

不,您不能创建字符串没有分配。您是说访问吗?为避免分配,您必须使用切片或指针来访问先前创建的字符串。虽然不确定演员,它可能会也可能不会为新字符串分配新的内存空间。

【讨论】:

  • OP 似乎愿意使用堆栈分配,所以我认为他只是想避免堆分配。
【解决方案4】:

解决此问题的一种方法是将可变字符复制到新的不可变版本中,然后对其进行切片:

void renderScore(int score)
{
    char[16] text;
    int n = sprintf(text.ptr, "Score: %d", score);
    immutable(char)[16] itext = text;
    renderText(itext[0..n]);
}

但是:

  1. 由于错误,DMD 目前不允许这样做。
  2. 您正在创建一个不必要的副本(比 GC 分配更好,但仍然不是很好)。

【讨论】:

  • 你确定这应该有效吗?引用 TDPL:“一个不可变的值是在 Stone 中铸造的:......它在整个程序的执行过程中永远不会改变”。我仍然认为堆栈分配的数据从未满足此要求。它可以用于局部变量,但前提是它们在编译时初始化,以便它们可以放入静态数据段而不是堆栈中。但是您的示例仍然必须在运行时设置 itext --> 堆栈空间。
  • @jpf:不变性和生命周期是正交问题。不变性保证它在其生命周期内不会改变,但不保证无限生命周期。如果你引用了一些生命周期有限的东西,那么你要确保你不会在破坏范围内使用它。
  • 看来你是对的。 TDPL 在那里有点不一致,在第 288 页上它清楚地说“一旦它被初始化,你不妨认为它已被永久烧入存储它的内存中。它在整个 程序的执行过程中永远不会改变”,恕我直言,这也意味着无限的寿命。但是在第 401 页上它说“一个不可变的值保证在整个它的生命周期内永远不会改变”
猜你喜欢
  • 2013-04-15
  • 2014-06-17
  • 2011-03-22
  • 2017-10-18
  • 1970-01-01
  • 2016-04-07
  • 1970-01-01
  • 1970-01-01
  • 2021-07-27
相关资源
最近更新 更多