나는 언어와 그 성격, 그리고 내 도메인뿐만 아니라 언어를 사용하는 방식에 따라 C ++로 그러한 개념을 적용하는 것으로 편견이 있습니다. 그러나 이런 것들이 주어지면 불변의 디자인 이라고 생각 합니다 때, 스레드 안전성, 시스템에 대한 추론의 용이성, 함수에 대한 더 많은 재사용 찾기 (및 우리가 할 수있는 발견)와 같은 기능적 프로그래밍과 관련된 많은 이점을 얻을 때 은 가장 흥미로운 측면 합니다. 불쾌한 놀라움없이 순서대로 결합하십시오) 등
이 간단한 C ++ 예제를 보자 (간단히 이미지 처리 전문가 앞에서 당황하지 않도록 단순성에 최적화되지는 않음) :
// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
{
Image dst(new_w, new_h);
for (int y=0; y < new_h; ++y)
{
for (int x=0; x < new_w; ++x)
dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
}
return dst;
}
이 함수의 구현은 두 개의 카운터 변수와 출력을위한 임시 로컬 이미지 형태로 로컬 (및 임시) 상태를 변경하지만 외부 부작용은 없습니다. 이미지를 입력하고 새로운 이미지를 출력합니다. 우리는 그것을 마음의 내용에 멀티 스레딩 할 수 있습니다. 추론하기 쉽고 철저한 테스트가 용이합니다. 무언가가 던져지면 새로운 이미지가 자동으로 버려지고 외부 부작용을 롤백 할 염려가 없기 때문에 예외가 안전합니다 (외부 이미지는 함수 범위 밖에서 수정되지 않습니다).
Image
위의 함수를 구현하기가 더 어려워지고 아마도 덜 효율적으로 만드는 것을 제외하고는 위의 컨텍스트에서 C ++로 불변 으로 만들면 거의 얻지 못할 가능성이 거의 없습니다.
청정
따라서 외부 기능이없는 순수한 기능 은 매우 흥미 롭습니다. C ++에서도 팀원에게 자주 선호하는 것이 중요합니다. 그러나 문맥과 뉘앙스가 거의없는 상태에서 적용되는 불변의 디자인은 언어의 명령 적 특성을 고려할 때 효율적으로 (일부 두 가지 임시 객체를 변경하는 것이 유용하고 실용적이기 때문에 거의 흥미롭지 않습니다. 순수한 기능을 구현하는 개발자 및 하드웨어).
무거운 구조의 저렴한 복사
내가 찾은 두 번째로 가장 유용한 속성은 엄격한 입력 / 출력 특성으로 함수를 순수하게 만들기 위해 종종 발생하는 것과 같이 비용이 많이 드는 데이터 구조를 저렴하게 복사하는 능력입니다. 이것들은 스택에 맞는 작은 구조가 아닙니다. 그것들 Scene
은 비디오 게임 의 전체와 같이 크고 무겁 습니다.
이 경우 복사 오버 헤드는 효과적인 병렬 처리의 기회를 방지 할 수 있습니다. 물리가 렌더러가 동시에 그리려고하는 장면을 변경하면서 동시에 물리를 깊게하는 경우 서로를 잠그고 병목 현상없이 효과적으로 병렬화하고 렌더링하는 것이 어려울 수 있기 때문입니다. 물리를 적용한 상태에서 하나의 프레임을 출력하기 위해 전체 게임 씬을 복사하면 효과가 동일하지 않을 수 있습니다. 그러나 물리 시스템이 단순히 장면을 입력하고 물리가 적용된 새로운 장면을 출력한다는 의미에서 '순수한'경우, 그러한 순도는 천문학적 복사 오버 헤드 비용으로 오지 않았지만 안전하게 하나는 다른 쪽을 기다리지 않고 렌더러입니다.
따라서 응용 프로그램 상태의 엄청나게 많은 데이터를 저렴하게 복사하고 최소한의 처리 비용과 메모리 사용으로 새로운 수정 버전을 출력하는 기능은 순도 및 효과적인 병렬 처리를위한 새로운 문을 열 수 있습니다. 지속적인 데이터 구조가 구현되는 방식. 그러나 우리가 그러한 교훈을 사용하여 만든 것은 완전히 영구적이거나 불변의 인터페이스를 제공 할 필요가 없습니다 (예를 들어, 복사시 복사 또는 "빌더 / 일시적"을 사용할 수 있음). 함수 / 시스템 / 파이프 라인의 병렬성과 순도 추구에서 메모리 사용과 메모리 액세스를 두 배로 늘리지 않고 복사본의 일부만 복사하고 수정합니다.
불변성
마지막으로 불변성은이 세 가지 중 가장 흥미롭지 않다고 생각하지만 특정 객체 디자인이 순수한 기능에 대한 로컬 임시로 사용되지 않고 넓은 맥락에서 귀중한 경우 철제 주먹으로 시행 할 수 있습니다 모든 방법에서 더 이상 외부 부작용을 일으키지 않기 때문에 "개체 수준 순도"의 종류 (더 이상 메서드의 로컬 범위를 벗어나는 멤버 변수를 더 이상 변경하지 않음).
그리고 C ++과 같은 언어 에서이 세 가지 중에서 가장 흥미 롭지 않다고 생각하지만 사소한 객체의 테스트 및 스레드 안전성 및 추론을 확실히 단순화 할 수 있습니다. 예를 들어 생성자 외부에서 객체에 고유 한 상태 조합을 제공 할 수 없으며, constness와 read- 원래 내용이 변경되지 않도록 (반드시 언어 내에서 가능한 한 많이) 반복자와 핸들 등을 보장합니다.
그러나 나는 이것이 가장 흥미로운 속성이라고 생각한다. 왜냐하면 내가보기에 대부분의 객체가 일시적으로, 가변적 인 형태로 사용되어 순수한 기능 (또는 "순수 시스템"과 같은 더 넓은 개념, 객체 또는 일련의 단순히 무언가를 입력하고 다른 것을 건드리지 않고 새로운 것을 출력하는 궁극적 인 효과를 갖는 기능), 나는 매우 명령적인 언어로 사지에 불변성을 가져 오는 것이 오히려 반 생산적인 목표라고 생각합니다. 실제로 가장 도움이되는 코드베이스 부분에는 거의 적용하지 않았습니다.
드디어:
[...] 영구적 인 데이터 구조 자체는 한 스레드가 다른 스레드에서 볼 수있는 변경을 수행하는 시나리오를 처리하기에 충분하지 않은 것 같습니다. 이를 위해서는 원자, 참조, 소프트웨어 트랜잭션 메모리 또는 클래식 잠금 및 동기화 메커니즘과 같은 장치를 사용해야합니다.
당연히 디자인이 여러 스레드에서 발생하는 수정 (사용자 엔드 디자인 의미)을 동시에 요구하면 동기화 또는 최소한 드로잉 보드로 돌아가서이를 처리 할 수있는 정교한 방법 ( 함수 프로그래밍에서 이러한 종류의 문제를 다루는 전문가가 사용하는 매우 정교한 예제를 보았습니다.)
그러나 일단 일종의 복사 및 일부 수정 된 무거운 구조를 출력하는 기능이 더러워지면 저렴하다는 것을 알았습니다. 예를 들어 지속적인 데이터 구조를 사용하면 종종 많은 문과 기회가 열립니다. 엄격한 I / O 종류의 병렬 파이프 라인에서 서로 독립적으로 실행될 수있는 코드를 병렬화하기 전에는 생각하지 않았습니다. 알고리즘의 일부가 본질적으로 직렬화되어야하더라도, 단일 스레드에 대한 처리를 연기 할 수 있지만 이러한 개념에 기대어 있으면 쉽게 걱정할 필요없이 무거운 작업의 90 %를 병렬화 할 수 있습니다.