2010-08-05 31 views
8

Tôi nhìn một chút tại mảng động trong D2, và tôi thấy chúng rất khó hiểu. Nó cũng có vẻ như tôi đang giải thích các spec sai .. Làm việc trên một tham chiếu hoặc lát của một mảng động có vẻ rất dễ bị lỗi khi thay đổi mảng ... Hoặc tôi chỉ không hiểu những nguyên tắc cơ bản?Thực hành không tốt để thay đổi mảng động có tham chiếu đến chúng?

Đề cập đến cùng một mảng chỉ chia sẻ các mục thực tế:

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); 

Khi họ tham khảo cùng một mảng, thay đổi một thay đổi khác:

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

Từ spec trên mảng

Để tối đa hóa hiệu quả, thời gian chạy luôn cố gắng thay đổi kích cỡ mảng để tránh thêm sao chép. Nó sẽ luôn luôn làm một bản sao nếu kích thước mới lớn hơn và mảng không được cấp phát qua toán tử mới hoặc hoạt động thay đổi kích thước trước đó .

Vì vậy, thay đổi độ dài không neccesarily phá vỡ các tài liệu tham khảo:

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]); 

Từ spec trên mảng

Concatenation luôn tạo ra một bản sao của toán hạng của nó, ngay cả khi một trong những toán hạng là mảng có độ dài 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 

Nhưng khi các mảng sẽ dẫm lên nhau, các giá trị được sao chép vào một vị trí mới và tham chiếu bị hỏng:

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); 

Thay đổi chiều dài của cả hai mảng trước khi thực hiện một sự thay đổi mang lại cho kết quả tương tự như trên (tôi sẽ hy vọng điều này đưa ra ở trên):

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); 

Và tương tự khi thay đổi chiều dài hoặc cancatenating (tôi mong chờ này đưa ra ở trên):

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

Nhưng sau đó lát cũng đi vào hình ảnh, và đột nhiên nó thậm chí còn phức tạp hơn! Các lát có thể bị mồ côi ...

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]); 

Vậy ... Thực tiễn không tốt để có nhiều tham chiếu đến cùng một mảng động? Và đi qua lát xung quanh vv? Hay tôi chỉ cần ra khỏi đây, thiếu toàn bộ điểm của mảng động trong D?

Trả lời

10

Nhìn chung, bạn có vẻ hiểu những điều khá rõ ràng, nhưng dường như bạn hiểu sai mục đích của thuộc tính ptr. Nó không không cho biết liệu hai mảng có tham chiếu đến cùng một phiên bản hay không. Những gì nó làm là có được bạn ở con trỏ đến những gì có hiệu quả mảng C bên dưới. Một mảng trong D có length của nó như là một phần của nó, do đó, nó giống như một cấu trúc với chiều dài và con trỏ đến một mảng C hơn là nó giống như một mảng C. ptr cho phép bạn truy cập vào mảng C và chuyển nó tới mã C hoặc C++.Bạn có lẽ không nên sử dụng nó cho bất cứ điều gì trong mã D thuần túy. Nếu bạn muốn kiểm tra xem hai biến mảng tham khảo các ví dụ tương tự, sau đó bạn sử dụng toán tử is (hoặc !is để kiểm tra rằng họ đang hợp khác nhau):

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

Tất cả những gì ptr là như nhau cho hai mảng sẽ chỉ ra rằng phần tử đầu tiên của chúng nằm ở cùng một vị trí trong bộ nhớ. Đặc biệt, length s của chúng có thể khác nhau. Tuy nhiên, nó có nghĩa là mọi phần tử chồng chéo sẽ bị thay đổi trong cả hai mảng nếu bạn thay đổi chúng trong một trong số chúng.

Khi thay đổi length của một mảng, D cố gắng tránh phân bổ lại, nhưng nó có thể quyết định phân bổ lại, vì vậy bạn không nhất thiết phải dựa vào việc nó có phân bổ lại hay không. Ví dụ, nó sẽ tái phân bổ nếu không làm như vậy sẽ dẫm lên bộ nhớ của mảng khác (bao gồm cả những bộ nhớ có cùng giá trị cho ptr). Nó cũng có thể tái phân bổ nếu không có đủ bộ nhớ để tự thay đổi kích thước tại chỗ. Về cơ bản, nó sẽ tái phân bổ nếu không làm như vậy sẽ dậm vào bộ nhớ của một mảng khác, và nó có thể hoặc có thể không phân bổ lại cách khác. Vì vậy, nói chung không phải là một ý tưởng tốt để dựa vào việc một mảng sẽ phân bổ lại hay không khi bạn đặt length của nó. Tôi có thể dự kiến ​​sẽ luôn luôn sao chép cho mỗi tài liệu, nhưng mỗi bài kiểm tra của bạn, nó có vẻ như hành động giống như length (Tôi không biết điều đó có nghĩa là các tài liệu cần phải được cập nhật hay liệu đó có phải là một tài liệu hay không?). lỗi - tôi đoán là các tài liệu cần được cập nhật). Trong cả hai trường hợp, bạn chắc chắn không thể dựa vào các tham chiếu khác vào mảng đó để vẫn tham chiếu đến cùng một mảng sau khi gắn thêm.

Đối với lát, chúng hoạt động như mong đợi và được sử dụng rất cao trong D - đặc biệt là trong thư viện chuẩn, Phobos. Một slice là một phạm vi cho một mảng và dãy là một khái niệm cốt lõi trong Phobos. Tuy nhiên, giống như nhiều phạm vi khác, thay đổi vùng chứa mà phạm vi/slice có thể làm mất hiệu lực phạm vi/slice đó. Đó là lý do tại sao khi bạn sử dụng các chức năng có thể thay đổi kích thước vùng chứa trong Phobos, bạn cần phải sử dụng các hàm được thêm vào ổn định (ví dụ: stableRemove() hoặc stableInsert()) nếu bạn không muốn rủi ro làm mất hiệu lực phạm vi mà bạn có cho vùng chứa đó.

Ngoài ra, một lát là một mảng giống như mảng mà nó trỏ đến. Vì vậy, một cách tự nhiên, thay đổi length hoặc nối thêm nó sẽ tuân theo tất cả các quy tắc tương tự như để thay đổi length hoặc nối thêm vào bất kỳ mảng nào khác, và do đó có thể được phân bổ lại và không còn là một lát nữa.

Khá nhiều, bạn chỉ cần lưu ý rằng việc thay đổi length của mảng theo bất kỳ cách nào có thể dẫn đến phân bổ lại, vì vậy bạn cần tránh làm điều đó nếu bạn muốn tham chiếu tiếp tục tham chiếu đến cùng một mảng. Và nếu bạn hoàn toàn cần đảm bảo rằng họ thực hiện không phải là trỏ đến cùng một tham chiếu, thì bạn cần sử dụng dup để nhận bản sao mới của mảng. Nếu bạn không lộn xộn với length của một mảng ở tất cả, sau đó tham chiếu mảng (là chúng lát hoặc tham chiếu đến toàn bộ mảng) sẽ tiếp tục vui vẻ tham khảo cùng một mảng.

EDIT: Hóa ra là cần phải cập nhật tài liệu. Bất cứ thứ gì có thể thay đổi kích cỡ mảng sẽ cố gắng thực hiện tại chỗ nếu nó có thể (để nó có thể không phân bổ lại) nhưng sẽ phân bổ lại nếu nó có để tránh dẫm lên bộ nhớ của mảng khác hoặc nếu nó không có đủ dung lượng để tái phân bổ tại chỗ. Vì vậy, không nên có bất kỳ sự phân biệt nào giữa việc thay đổi kích thước mảng bằng cách thiết lập thuộc tính length và thay đổi kích thước của nó bằng cách gắn thêm nó.

THÊM: Bất kỳ ai sử dụng D thực sự nên đọc this article trên mảng và lát. Nó giải thích chúng khá tốt, và sẽ cho bạn ý tưởng tốt hơn về cách mảng hoạt động trong D.

+0

Cảm ơn câu trả lời chi tiết, tốt. Tôi cũng đã nghe nói về thuộc tính .capacity cho biết độ dài tối đa mà mảng có thể có trước khi nó cần tái phân bổ. Khi thực hiện "auto b = a", chúng sẽ có tham chiếu khác; & a! = & b, nhưng có vẻ như "is" sử dụng .ptr dưới mui xe để kiểm tra tính bình đẳng tham chiếu. – simendsjo

2

Tôi thực sự không muốn đưa câu trả lời này thành câu trả lời đầy đủ, nhưng tôi chưa thể nhận xét về câu trả lời trước.

Tôi nghĩ rằng ghép nối và nối thêm là hai hoạt động hơi khác nhau. Nếu bạn sử dụng ~ với một mảng và một phần tử, nó sẽ phụ thêm; với hai mảng, đó là nối.

Bạn có thể thử này để thay thế:

a = a ~ 2; 

Và xem nếu bạn nhận được kết quả tương tự.

Ngoài ra, nếu bạn muốn có hành vi được xác định, chỉ cần sử dụng các thuộc tính .dup (hoặc .idup for immutables). Điều này cũng rất hữu ích nếu bạn có một mảng tham chiếu; bạn có thể sửa đổi mảng chính và các lát .dup để làm việc mà không phải lo lắng về điều kiện chủng tộc.

EDIT: ok, tôi đã nhận được một chút sai, nhưng có nó là anyway. Ghép nối! = Chắp thêm.

// Tối đa

+0

Có, một người khác chỉ ra điều đó cho tôi, nhưng tôi không thể tìm thấy điều này trong spec ... Nó chỉ nói rằng "a op = b" là giống như "a = a op b". Không thể tìm thấy sự khác biệt mảng trong spec .. – simendsjo

+0

Tôi nghĩ rằng tôi nhớ nó từ TDPL của Andrei (cuốn sách). Ít nhất tôi chắc chắn tôi đã đọc một cách rõ ràng điều này ở đâu đó. – awishformore

+1

Thực ra, 'op' và' op = 'bị quá tải riêng biệt, vì vậy nếu các tài liệu nói cách khác, chúng sai. Bạn quá tải 'op' với' opBinary' và 'op =' với 'opAssign'. Vì vậy, họ về lý thuyết có thể làm những điều hoàn toàn riêng biệt (mặc dù họ không nên). Tôi tin rằng nó được thực hiện theo cách đó để cho phép tối ưu hóa vì 'op =' có thể thực hiện các công cụ thay vì phải tạo ra một giá trị mới với kết quả. Vì vậy, nếu các mảng không hoạt động giống với '~' như chúng làm với '~ =', thì nó không đặc biệt đặc biệt. –

Các vấn đề liên quan