C ++에서 생성자 및 = 연산자 오버로드 복사 : 공통 함수가 가능합니까?


87

복사 생성자 이후

MyClass(const MyClass&);

및 = 연산자 오버로드

MyClass& operator = (const MyClass&);

거의 동일한 코드, 동일한 매개 변수, 반환시에만 차이가있는 경우 둘 다 사용할 수있는 공통 기능을 가질 수 있습니까?


6
"... 거의 같은 코드가 ..."? 흠 ... 뭔가 잘못하고있는 게 분명 해요. 이를 위해 사용자 정의 함수를 사용할 필요성을 최소화하고 컴파일러가 모든 더러운 작업을 수행하도록하십시오. 이것은 종종 자체 구성원 개체에 리소스를 캡슐화하는 것을 의미합니다. 코드를 보여 주시면됩니다. 좋은 디자인 제안이있을 수 있습니다.
sellibitze

답변:


122

예. 두 가지 일반적인 옵션이 있습니다. 일반적으로 권장되지 않는 하나 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작동하며 모든 기본 유형 및 포인터 유형으로 '투척 금지'가 보장됩니다. 대부분의 스마트 포인터는 던지지 않음 보증으로 교체 할 수도 있습니다.


3
실제로 그들은 일반적인 작업이 아닙니다. 복사기가 처음으로 객체의 멤버를 초기화하는 동안 할당 연산자는 기존 값을 재정의합니다. 이것을 고려할 때, operator=카피 ctor의 alling은 사실 꽤 나쁩니다. 왜냐하면 그것은 먼저 모든 값을 어떤 기본값으로 초기화하여 바로 후에 다른 객체의 값으로 재정의하기 때문입니다.
sbi

14
"권장하지 않음"에 "그리고 C ++ 전문가도 마찬가지"를 추가 할 수 있습니다. 누군가가 와서 당신이 단지 개인적인 소수 선호를 표현하는 것이 아니라 실제로 그것에 대해 생각한 사람들의 합의 된 의견을 표현하고 있다는 것을 깨닫지 못할 수도 있습니다. 그리고 좋아요, 아마도 제가 틀렸고 일부 C ++ 전문가가 그것을 추천하지만, 개인적으로 나는 누군가가 그 추천에 대한 참조를 내놓을 수 있도록 도전을 계속하고 있습니다.
Steve Jessop

4
충분히 공평합니다, 어쨌든 이미 당신을 찬성했습니다 :-). 나는 어떤 것이 모범 사례로 널리 간주된다면 그렇게 말하는 것이 가장 좋다고 생각합니다 (그리고 누군가가 그것이 실제로 최선이 아니라고 말하면 다시보십시오). 마찬가지로 누군가 "C ++에서 뮤텍스를 사용할 수 있습니까?"라고 물으면 "일반적인 옵션 중 하나는 RAII를 완전히 무시하고 프로덕션에서 교착 상태가되는 비 예외 안전 코드를 작성하는 것입니다. 괜찮은 작업 코드 ";-)
Steve Jessop

4
+1. 그리고 항상 분석이 필요하다고 생각합니다. assign어떤 경우에는 (경량 클래스의 경우) 복사 ctor와 할당 연산자가 모두 사용 하는 멤버 함수 를 갖는 것이 합리적이라고 생각합니다 . 다른 경우 (자원 집약적 / 사용 사례, 핸들 / 본문)에서는 물론 복사 / 교환이 가능합니다.
Johannes Schaub-litb

2
@litb : 나는 이것에 놀랐 기 때문에 Exception C ++에서 항목 41을 찾아 봤는데 (이것이 바뀌 었습니다)이 특별한 권장 사항은 사라졌고 그는 그 자리에 복사 및 교체를 권장합니다. 오히려 그는 동시에 "문제 # 4 : 할당에 비효율적이다"를 몰래 떨어 뜨렸다.
CB Bailey

13

복사 생성자는 원시 메모리였던 개체를 처음으로 초기화합니다. 할당 연산자 OTOH는 기존 값을 새 값으로 재정의합니다. 일반적으로 여기에는 오래된 리소스 (예 : 메모리)를 해제하고 새 리소스를 할당하는 작업이 포함됩니다.

둘 사이에 유사점이 있다면 할당 연산자가 파괴와 복사 생성을 수행한다는 것입니다. 일부 개발자는 배치 복사 구성에 이은 내부 파괴에 의해 실제로 할당을 구현했습니다. 그러나 이것은 매우 나쁜 생각입니다. (파생 클래스를 할당하는 동안 호출 한 기본 클래스의 할당 연산자 인 경우 어떻게해야합니까?)

오늘날 일반적으로 표준 관용구로 간주되는 swap것은 Charles가 제안한대로 사용 하고 있습니다.

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

이것은 복사 생성 ( other복사 된 것에 주의 )과 파괴 (함수 끝에서 파괴됨)를 사용하며, 또한 올바른 순서로 사용합니다 : 파괴 전 생성 (실패하지 않아야 함).


swap선언 해야합니까 virtual?

1
@Johannes : 가상 함수는 다형성 클래스 계층에서 사용됩니다. 할당 연산자는 값 유형에 사용됩니다. 두 사람은 거의 섞이지 않습니다.
sbi

-3

뭔가 신경 쓰이는 부분 :

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이 두 번 해제되지 않도록).

{구성, 이동, 파괴} 대신 {구성, 파괴, 이동}을 제안합니다. 가장 위험한 행동 인 움직임은 다른 모든 것이 해결 된 후 마지막으로 취하는 행동입니다.

예, 파괴 실패는 두 계획 모두에서 문제입니다. 데이터가 손상되었거나 (생각하지 않았을 때 복사 됨) 손실되었습니다 (생각하지 않았을 때 해제 됨). 분실은 타락한 것보다 낫습니다. 나쁜 데이터보다 좋은 데이터는 없습니다.

스왑 대신 양도. 어쨌든 그것은 내 제안입니다.


2
소멸자는 실패하지 않아야하므로 파괴시 예외가 예상되지 않습니다. 그리고 이동이 가장 위험한 작업이라면 파괴 뒤로 이동하는 것의 이점이 무엇인지 이해하지 못합니다. 즉, 표준 체계에서 이동 실패는 이전 상태를 손상시키지 않지만 새 체계는 손상시킵니다. 왜? 또한, First, reading the word "swap" when my mind is thinking "copy" irritates-> 도서관 작성자로서 일반적으로 일반적인 관행 (복사 + 스왑)을 알고 있으며 핵심은 my mind. 당신의 마음은 실제로 공개 인터페이스 뒤에 숨겨져 있습니다. 이것이 재사용 가능한 코드의 전부입니다.
Sebastian Mach
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.