복사 생성자 이후
MyClass(const MyClass&);
및 = 연산자 오버로드
MyClass& operator = (const MyClass&);
거의 동일한 코드, 동일한 매개 변수, 반환시에만 차이가있는 경우 둘 다 사용할 수있는 공통 기능을 가질 수 있습니까?
복사 생성자 이후
MyClass(const MyClass&);
및 = 연산자 오버로드
MyClass& operator = (const MyClass&);
거의 동일한 코드, 동일한 매개 변수, 반환시에만 차이가있는 경우 둘 다 사용할 수있는 공통 기능을 가질 수 있습니까?
답변:
예. 두 가지 일반적인 옵션이 있습니다. 일반적으로 권장되지 않는 하나 operator=
는 복사 생성자에서를 명시 적으로 호출하는 것 입니다.
MyClass(const MyClass& other)
{
operator=(other);
}
그러나 재화를 제공하는 것은 operator=
이전 상태와 자기 할당으로 인해 발생하는 문제를 다룰 때 도전입니다. 또한 모든 멤버와베이스는에서 할당 되더라도 먼저 기본값이 초기화됩니다 other
. 이것은 모든 구성원과 기반에 대해 유효하지 않을 수도 있으며 유효한 경우에도 의미 상 중복되고 실질적으로 비용이 많이들 수 있습니다.
점점 더 많이 사용되는 솔루션은 operator=
복사 생성자와 스왑 메서드를 사용하여 구현 하는 것입니다.
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
또는:
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
swap
단지 내부의 소유권을 바꿉니다 기존 상태를 정리하거나 새로운 자원을 할당 할 필요가 없기 때문에 기능은 쓰기에 일반적으로 간단합니다.
복사 및 스왑 관용구의 장점은 자동 할당이 안전하고 스왑 작업이 던지지 않는 경우에도 예외적으로 안전하다는 것입니다.
강력한 예외 안전을 위해 '수작업'으로 작성된 할당 연산자는 일반적으로 할당 자의 이전 리소스를 할당 해제하기 전에 새 리소스의 복사본을 할당해야합니다. 그래야 새 리소스를 할당하는 동안 예외가 발생하더라도 이전 상태가 계속 반환 될 수 있습니다. . 이 모든 것은 복사 및 교체와 함께 무료로 제공되지만 일반적으로 더 복잡하므로 처음부터 오류가 발생하기 쉽습니다.
주의해야 할 한 가지는 스왑 메서드가 std::swap
복사 생성자와 할당 연산자 자체를 사용하는 기본값 이 아닌 진정한 스왑인지 확인하는 것입니다.
일반적으로 멤버 별 swap
이 사용됩니다. std::swap
작동하며 모든 기본 유형 및 포인터 유형으로 '투척 금지'가 보장됩니다. 대부분의 스마트 포인터는 던지지 않음 보증으로 교체 할 수도 있습니다.
operator=
카피 ctor의 alling은 사실 꽤 나쁩니다. 왜냐하면 그것은 먼저 모든 값을 어떤 기본값으로 초기화하여 바로 후에 다른 객체의 값으로 재정의하기 때문입니다.
assign
어떤 경우에는 (경량 클래스의 경우) 복사 ctor와 할당 연산자가 모두 사용 하는 멤버 함수 를 갖는 것이 합리적이라고 생각합니다 . 다른 경우 (자원 집약적 / 사용 사례, 핸들 / 본문)에서는 물론 복사 / 교환이 가능합니다.
복사 생성자는 원시 메모리였던 개체를 처음으로 초기화합니다. 할당 연산자 OTOH는 기존 값을 새 값으로 재정의합니다. 일반적으로 여기에는 오래된 리소스 (예 : 메모리)를 해제하고 새 리소스를 할당하는 작업이 포함됩니다.
둘 사이에 유사점이 있다면 할당 연산자가 파괴와 복사 생성을 수행한다는 것입니다. 일부 개발자는 배치 복사 구성에 이은 내부 파괴에 의해 실제로 할당을 구현했습니다. 그러나 이것은 매우 나쁜 생각입니다. (파생 클래스를 할당하는 동안 호출 한 기본 클래스의 할당 연산자 인 경우 어떻게해야합니까?)
오늘날 일반적으로 표준 관용구로 간주되는 swap
것은 Charles가 제안한대로 사용 하고 있습니다.
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
이것은 복사 생성 ( other
복사 된 것에 주의 )과 파괴 (함수 끝에서 파괴됨)를 사용하며, 또한 올바른 순서로 사용합니다 : 파괴 전 생성 (실패하지 않아야 함).
뭔가 신경 쓰이는 부분 :
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
첫째, 내 마음이 "복사"를 생각할 때 "스왑"이라는 단어를 읽는 것은 내 상식을 자극합니다. 또한이 멋진 트릭의 목표에 의문을 제기합니다. 예, 새 (복사 된) 리소스를 구성하는 모든 예외는 스왑 전에 발생해야합니다. 이는 모든 새 데이터가 활성화되기 전에 채워 졌는지 확인하는 안전한 방법처럼 보입니다.
괜찮아. 그렇다면 스왑 후에 발생하는 예외는 어떻습니까? (임시 객체가 범위를 벗어 났을 때 이전 리소스가 소멸되는 경우) 할당 사용자의 관점에서 작업이 실패한 것을 제외하고는 실패했습니다. 엄청난 부작용이 있습니다. 복사가 실제로 일어났습니다. 실패한 것은 일부 리소스 정리뿐이었습니다. 외부에서 작업이 실패한 것처럼 보이지만 대상 개체의 상태가 변경되었습니다.
그래서 저는 "스왑"대신에보다 자연스러운 "이전"을 제안합니다.
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
transfer(tmp);
return *this;
}
여전히 임시 개체의 구성이 있지만 다음 즉각적인 조치는 소스의 리소스를 이동하기 전에 대상의 모든 현재 리소스를 해제하는 것입니다 (그리고 NULL이 두 번 해제되지 않도록).
{구성, 이동, 파괴} 대신 {구성, 파괴, 이동}을 제안합니다. 가장 위험한 행동 인 움직임은 다른 모든 것이 해결 된 후 마지막으로 취하는 행동입니다.
예, 파괴 실패는 두 계획 모두에서 문제입니다. 데이터가 손상되었거나 (생각하지 않았을 때 복사 됨) 손실되었습니다 (생각하지 않았을 때 해제 됨). 분실은 타락한 것보다 낫습니다. 나쁜 데이터보다 좋은 데이터는 없습니다.
스왑 대신 양도. 어쨌든 그것은 내 제안입니다.
First, reading the word "swap" when my mind is thinking "copy" irritates
-> 도서관 작성자로서 일반적으로 일반적인 관행 (복사 + 스왑)을 알고 있으며 핵심은 my mind
. 당신의 마음은 실제로 공개 인터페이스 뒤에 숨겨져 있습니다. 이것이 재사용 가능한 코드의 전부입니다.