2010-08-05 5 views
8

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

효율성을 극대화하기 위해 런타임은 항상 여분의 공간을 피하기 위해 배열의 크기를 조정하려고 시도합니다 복사. 새 크기가 더 크고 배열이 new 연산자 또는 이전 크기 조정 작업을 통해 할당되지 않은 경우에는 항상 복사본 을 수행합니다.

그래서 굳이 용 참조 중단하지 않는 길이를 변경 : 배열에의

병합은 항상 피연산자의 복사본을 생성

, 경우에도 하나를

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

그리고 같은 변화 길이 또는 cancatenating는 (나는 이것이 위의 주어진 기대 : 예상이) 위의 주어진

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에서 동적 배열의 전체 점을 놓치고 있습니다.

답변

10

전반적으로 상당히 잘 이해하는 것 같지만 ptr 속성의 목적을 오해하고있는 것으로 보입니다. 이 아님은 두 배열이 같은 인스턴스를 참조하는지 여부를 나타냅니다. 그것이하는 일은 실제로 밑의 C 배열이 무엇인지에 대한 포인터를 얻는 것입니다. D에있는 배열은 그것의 일부로 length을 가지고 있습니다. 그래서 C 배열보다 길이가 길고 C 배열에 대한 포인터를 가진 구조체와 같습니다. ptr을 사용하면 C 배열에서 C 또는 C++ 코드로 전달할 수 있습니다.순수한 D 코드에서는 아무 것도 쓰지 않아야합니다. 두 배열 변수가 같은 인스턴스를 참조하는지 여부를 테스트하려면, 당신은 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 두 배열 같다고 것 첫 번째 요소가 같은 위치에 있다는 것을 나타냅니다. 특히 length은 다를 수 있습니다. 그러나 중첩 된 요소 중 하나에서 요소를 변경하면 겹치는 요소가 두 배열에서 변경된다는 것을 의미합니다.

배열의 length을 변경하면 D가 재 할당을 시도하지만 재 할당을 결정할 수 있으므로 재 할당 여부에 의존 할 필요가 없습니다. 예를 들어 재 할당을하지 않으면 다른 배열의 메모리 (ptr과 동일한 값을 가진 메모리를 포함하여)를 스톰프합니다. 또한 크기가 조정될 메모리가 충분하지 않은 경우에도 다시 할당 할 수 있습니다. 기본적으로, 그렇게하지 않으면 다른 배열의 메모리를 밟아서 재 할당 할 수 있습니다. 그렇지 않으면 재 할당 할 수도 있고 재 할당하지 않을 수도 있습니다. 따라서 일반적으로 어레이를 재 할당할지 여부에 의존하는 것이 좋지 않습니다. length을 설정하면됩니다.

항상 문서 당 복사하는 기능이 추가 될 것으로 예상되었지만 테스트 할 때 length처럼 작동하는 것으로 보입니다. (즉, 문서를 업데이트해야하는지, 아니면 버그 - 내 추측은 문서를 업데이 트해야 할 것입니다). 두 경우 모두, 해당 배열에 대한 다른 참조를 신뢰할 수 없기 때문에 추가 한 후 동일한 배열을 계속 참조 할 수 있습니다.

슬라이스의 경우와 마찬가지로 예상대로 작동하며 D - 특히 표준 라이브러리 인 Phobos에서 많이 사용됩니다. 슬라이스는 배열의 범위이며 범위는 Phobos의 핵심 개념입니다. 그러나 다른 많은 범위와 마찬가지로 범위/조각이있는 컨테이너를 변경하면 해당 범위/조각이 무효화 될 수 있습니다. 그렇기 때문에 Phobos에서 컨테이너의 크기를 조정할 수있는 기능을 사용하는 경우 해당 컨테이너의 범위를 무효화하지 않으려면 안정적으로 앞에 붙인 기능 (예 : stableRemove() 또는 stableInsert())을 사용해야합니다.

또한 조각은 가리키는 배열과 같은 배열입니다. 따라서 length을 변경하거나 추가하는 것은 자연스럽게 다른 배열의 length을 변경하거나 추가하는 규칙과 동일한 규칙을 따르므로 재 할당 할 수 있고 더 이상 다른 배열의 조각이 될 수 없습니다.

배열의 length을 변경하면 재 할당이 발생할 수 있으므로 참조가 동일한 배열 인스턴스를 계속 참조하도록하려면이 작업을 수행하지 않아야합니다. 그리고 그들이 반드시 이 아닌지 확인해야한다면은 동일한 참조를 가리키고 배열의 새 사본을 얻으려면 dup을 사용해야합니다. 배열의 length을 전혀 사용하지 않으면 배열 참조 (슬라이스 또는 전체 배열에 대한 참조)가 계속해서 동일한 배열을 계속 참조하게됩니다.

편집 : 문서를 업데이트해야한다는 것이 밝혀졌습니다. 배열의 크기를 조정할 수있는 모든 것은 그것이 가능하다면 (그렇게 재 할당하지 않을 수도 있지만) 다른 배열의 메모리를 밟지 않도록하거나 충분한 공간이없는 경우에 그것을 재 할당해야한다. 제자리에 재배치. 따라서 배열의 크기를 length으로 설정하고 크기를 추가하여 크기를 조정하면 배열의 크기를 구별하지 않아야합니다.

추가 : D를 사용하는 사람은 누구나 배열 및 조각에 대해 this article을 읽어야합니다. 그것들을 아주 잘 설명해 주며, 배열이 D에서 어떻게 작동하는지 훨씬 더 잘 이해할 수 있습니다.

+0

좋은 답변에 감사드립니다. 또한 배열의 재 할당이 필요할 때까지 배열의 최대 길이를 지정할 수있는 .capacity 속성에 대해서도 들었습니다. "auto b = a"를 수행하면 다른 참조를 얻습니다. & a! = & b, 그러나 "is"는 .ptr을 참조 평등을 검사하기 위해 사용합니다. – simendsjo

2

나는 이것을 본격적인 대답으로 만들고 싶지 않지만, 이전 답변.

연결 및 추가가 두 가지의 약간 다른 작업이라고 생각합니다. ~를 배열과 요소와 함께 사용하면 추가됩니다. 두 개의 배열로 연결됩니다.

a = a ~ 2; 

을 그리고 당신은 동일한 결과를 얻을 수 있는지 확인 :

대신이 시도 할 수 있습니다.

또한 동작을 정의하려면 .dup (또는 immutables에 대한 .idup) 속성 만 사용하십시오. 참조 배열이있는 경우에도 매우 유용합니다. 경합 조건에 대해 걱정하지 않고 메인 어레이 및 .dup 슬라이스를 수정할 수 있습니다.

편집 : 확인, 내가 조금 잘못되었지만 어쨌든 있습니다. 연결! = 추가.

// 최대

+0

예, 누군가 다른 사람이 저를 위해 지적했는데 스펙에서 이것을 찾을 수 없습니다 ... "a op = b"는 "a = a op b"와 같습니다. 사양에서 배열의 구별을 찾을 수 없습니다. – simendsjo

+0

나는 TDRE에서 Andrei (책)에 의해 그것을 기억한다고 생각합니다. 적어도 나는 분명히 이것을 어딘가에서 읽었을 것이라고 확신한다. – awishformore

+1

실제로,'op'와'op ='는 별도로 과부하가 걸립니다. 그래서 다른 말로하면 틀린 것입니다. 'opBinary'와'op ='opAssign'을 사용하여'op'를 오버로드합니다. 그래서 그들은 이론적으로 완전히 별개의 일을 할 수 있습니다. op =가 결과로 새로운 값을 생성하는 대신에 제자리에서 작업을 수행 할 수 있기 때문에 최적화가 가능하다는 결론을 얻었습니다. 배열이'~ = '과 똑같이 행동하지 않는다면'~ ='과 같이 처리하는 것이 특별히 특별하지 않습니다. –

관련 문제