代码之家  ›  专栏  ›  技术社区  ›  simendsjo

更改引用了动态数组的动态数组是一种不好的做法吗?

  •  8
  • simendsjo  · 技术社区  · 15 年前

    我看了一下D2中的动态数组,发现它们很难理解。看来我对说明书的解释也是错的。。 在更改数组时,处理动态数组的引用或切片似乎非常容易出错。。。或者我只是不了解基本原理?

    引用同一数组仅共享实际项:

    auto a = [1];
    auto b = a;
    assert(&a != &b); // different instance; Doesn't share length
    assert(a.ptr == b.ptr); // same items
    assert(a == [1]);
    assert(a == b);
    

    auto a = [1,2];
    auto b = a;
    a[1] = 20;
    assert(a == [1,20]);
    assert(a == b);
    

    为了最大限度地提高效率,运行时总是尝试调整 如果新大小更大,并且阵列未通过 新操作员或以前的操作员

    因此,更改长度并不一定会破坏参考:

    auto a = [1];
    auto b = a;
    b.length = 2;
    assert(b == [1,0]);
    assert(a == [1]); // a unchanged even if it refers to the same instance
    assert(a.ptr == b.ptr);  // but still the same instance
    
    // So updates to one works on the other
    a[0]  = 10;
    assert(a == [10]);
    assert(b == [10,0]);
    

    从阵列上的规范

    连接总是创建其操作数的副本,即使其中一个操作数是长度为0的数组

    auto a = [1];
    auto b = a;
    b ~= 2; // Should make a copy, right..?
    assert(a == [1]);
    assert(b == [1,2]);
    assert(a != b);
    assert(a4.ptr == b.ptr); // But it's still the same instance
    a[0] = 10;
    assert(b == [10,2]); // So changes to a changes b
    

    auto a = [1];
    auto b = a;
    b ~= 2;
    assert(a == [1]);
    assert(b == [1,2]);
    
    a.length = 2; // Copies values to new memory location to not overwrite b's changes
    assert(a.ptr != b.ptr);
    

    在进行更改之前更改两个数组的长度会得到与上面相同的结果(我希望这是在上面给出的情况下):

    auto a = [1];
    auto b = a;
    a.length = 2;
    b.length = 2;
    a[1] = 2;
    assert(a == [1,2]);
    assert(b == [1,0]);
    assert(a.ptr != b.ptr);
    

    当改变长度或癌变时也是如此(我希望这是鉴于上述情况):

    auto a = [1];
    auto b = a;
    b.length = 2;
    a ~= 2;
    assert(a == [1,2]);
    assert(b == [1,0]);
    assert(a.ptr != b.ptr);
    

    但随后切片也出现在画面中,突然变得更加复杂!切片可能是孤立的。。。

    auto a = [1,2,3];
    auto b = a;
    auto slice = a[1..$]; // [2,3];
    slice[0] = 20;
    assert(a == [1,20,3]);
    assert(a == b);
    
    a.length = 4;
    assert(a == [1,20,3,0]);
    slice[0] = 200;
    assert(b == [1,200,3]); // the reference to b is still valid.
    assert(a == [1, 20, 3, 0]); // but the reference to a is now invalid..
    
    b ~= 4;
    // Now both references is invalid and the slice is orphan...
    // What does the slice modify?
    assert(a.ptr != b.ptr);
    slice[0] = 2000;
    assert(slice == [2000,3]);
    assert(a == [1,20,3,0]); 
    assert(b == [1,200,3,4]);
    

    所以。。。对同一动态数组有多个引用是一种不好的做法吗?把片子传来传去。?或者我就在这里,错过了D中动态数组的全部要点?

    2 回复  |  直到 15 年前
        1
  •  10
  •   Jonathan M Davis    12 年前

    总的来说,你似乎对事情理解得相当透彻,但你似乎误解了会议的目的 ptr 财产。是的 length ptr公司 is 操作员(或 !is 检查它们是否是不同的实例):

    assert(a is b);   //checks that they're the same instance
    assert(a !is b);  //checks that they're *not* the same instance
    

    所有这些 ptr公司 如果两个数组相等,则表示它们的第一个元素在内存中位于同一位置。特别是他们的 长度 可能会有所不同。但是,这确实意味着,如果在两个数组中的一个数组中更改任何重叠的元素,那么这两个数组中的任何重叠元素都将被更改。

    在更改 对于一个数组,D试图避免重新分配,但是它可以决定重新分配,所以您不必依赖它是否会重新分配。例如,它将重新分配,如果不这样做,它将踩踏另一个数组的内存(包括那些具有相同值的内存) ptr公司 .

    我本以为附加总是按照文档进行复制,但根据您的测试,它的行为看起来就像 长度 是的(我不知道这是否意味着文档需要更新,或者是一个bug——我猜文档需要更新)。在这两种情况下,您当然不能依赖于对该数组的其他引用在附加之后仍然引用同一个数组。

    至于切片,它们的工作原理和预期一样,在D中得到了广泛的应用,尤其是在标准库Phobos中。切片是数组的范围,范围是火卫一的核心概念。但是,与许多其他范围一样,更改范围/切片所针对的容器可能会使该范围/切片无效。这就是为什么当你在Phobos中使用可以调整容器大小的函数时,你需要使用带有stable的函数(例如。 stableRemove() stableInsert() )如果您不想冒险使容器的范围无效。

    另外,切片是一个数组,就像它所指向的数组一样。所以,很自然地,改变它的 长度 或者附加在它之后,将遵循所有与更改 属于或附加到任何其他数组的,因此它可以重新分配,不再是另一个数组中的片。

    长度 以任何方式对数组的引用都可能导致重新分配,因此如果希望引用继续引用同一个数组实例,则需要避免这样做。如果你真的需要确保 指向相同的引用,则需要使用 dup 长度 那么数组引用(无论是切片还是对整个数组的引用)将继续愉快地引用同一个数组。

    编辑: 长度

    附录: 任何使用D的人都应该阅读 this article 在数组和切片上。它很好地解释了它们,应该可以让您更好地了解数组在D中的工作方式。

        2
  •  2
  •   awfm9    15 年前

    我认为连接和附加是两种稍有不同的操作。如果对数组和元素使用~;对于两个数组,它是串联的。

    a = a ~ 2;
    

    看看你能不能得到同样的结果。

    另外,如果您想定义行为,只需使用.dup(或.idup for immutables)属性。如果您有一个引用数组,这也是非常有用的;您可以修改主数组和.dup切片,以便在不考虑竞争条件的情况下进行计算。

    编辑:好吧,我弄错了一点,但它还是在那里。连接!=追加。