59
Effective C++ Chapter 1, 2 정정

Effective c++chapter1 and2

  • Upload
    -

  • View
    69

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Effective c++chapter1 and2

Effective C++ Chapter 1, 2 정리

Page 2: Effective c++chapter1 and2

Chapter 1

C++ 에 왔으면 C++ 의 법을 따릅시다

Page 3: Effective c++chapter1 and2

Item 1

C++ 을 언어의 연합체로 보자

Page 4: Effective c++chapter1 and2

Item 1 : C++ 을 언어의 연합체로 보자

- C++ 은 어렵다

- 그래도 C++ 의 산을 넘는 대원칙이 있다

- 바로 C++ 을 언어의 연합체로 보는 것

- C++ 의 여러 문제들과 맞닿게 되었을 때 ,

- C++ 이 4 개의 하위 언어로 구성됨을 알고

- 문제가 어떤 하위 언어와 연관되어 있는지 알면 , 더 쉽게 문제를 해결 할 수 있다 .

Page 5: Effective c++chapter1 and2

Item 1 : C++ 을 언어의 연합체로 보자

C++ 을 구성하는 4 개의 하위 언어들

1. C - 기본이다 . 블록 , 선행처리자 , 기본제공 데이터타입 , 배열 , 포인터 등이 C로부터 왔다

2. 객체지향의 C++- 클래스를 쓰는 ‘ C’ 포함 , 캡슐화 , 다형성 , 가상 함수 등등

3. 템플릿 C++- 뒤에서 다룰 템플릿 메타 프로그래밍과도 연관

4. STL- 템플릿 라이브러리 , 독특한 사용규약들을 알아야 한다

Page 6: Effective c++chapter1 and2

Item 2

#define 을 쓰려거든 const, In-line, enum 을 떠올리자

Page 7: Effective c++chapter1 and2

Item 2 : #define 을 쓰려거든 const, enum, inline

프로그래밍을 하다 보면 #define 을 쓰는 경우가 부지기수다

그러나 #define 에는 단점이 있다

대체 수단인 const, enum, inline 을 사용하도록 하자 . 되도록이면

Page 8: Effective c++chapter1 and2

Item 2 : #define 을 쓰려거든 const, enum, inline

단점 1 : #define 은 기호가 아니다 ex) #define ASPECT_RATIO 1.653

실제로 코드에 ASPECT_RATIO 가 쓰이는 부분은 기호가 아닌 1.653 이라는 수가 들어간다 .

어떤 곳에서 에러가 나면 1.653 이라는 메시지가 있을 뿐 !

이게 우리가 쓴 그 ASPECT_RATIO 인지 계산된 어떤 값인지 알 길이 없다

대체재 : const

const double AspectRatio = 1.653; // 기호이자 상수

Page 9: Effective c++chapter1 and2

Item 2 : #define 을 쓰려거든 const, enum, inline

단점 2 : #define 은 사본을 많이 만든다 ex) #define ASPECT_RATIO 1.653

우리가 ASPECT_RATIO 라고 써 놓은 모든 코드코드마다 사본이 생긴다

그러나 대체할 것들로 사용한다면 ? 메모리는 한 번 사용될 뿐이다

대체재 : const

const double AspectRatio = 1.653; // 사본은 딱 한 개만 생긴다

Page 10: Effective c++chapter1 and2

Item 2 : #define 을 쓰려거든 const, enum, inline

단점 3 : #define 은 클래스 상수를 정의할 수도 , 캡슐화 혜택을 받을 수도 없다 #define 은 private 캡슐화가 되지 않는다 . 전역으로 선언된 녀석이다 .

대체재 : static const

위의 예에서 numTurns 는 클래스 상수로 , 유효범위가 클래스이고 , 사본 개수가 한 개를 넘지 못한다 .

Page 11: Effective c++chapter1 and2

Item 2 : #define 을 쓰려거든 const, enum, inline

static const 도 대체할 수 있는 대체재 : enum

컴파일러에 따라서 사용상의 주의가 필요할 수 있는 static const 와 다른 간단한 enum.

#define 처럼 작동 하면서 ( 주소를 얻을 수 없다 . 참조도 .) 컴파일러에 따른 주의를 기울이지 않아도 된다 . 그리고 많은 코드가 이렇게 쓰여 있다 .

Page 12: Effective c++chapter1 and2

Item 2 : #define 을 쓰려거든 const, enum, inline

단점 4 : #define 의 매크로 함수는 단점이 많다 .

각 인자마다 괄호를 씌워줘야 하는 것 하며 , 잘 모르고 사용할 때 발생하는 문제들에 대한정확한 인지가 필요하다는 것 등등 .

대체재 : 인라인 템플릿

괄호를 많이 쓸 필요도 없고 , 유효범위는 그대로 지켜지면서 , 인자가 여러 번 평가 될 수 도 있는 상황이 만들어지지 않는다 .

Page 13: Effective c++chapter1 and2

Item 3

낌새만 보이면 const 를 들이대자

Page 14: Effective c++chapter1 and2

Item 3 : 낌새만 보이면 const 를 들이대 보자

BIG 장점 : 1. 의미적인 제약 = 컴파일러에 의해 외부변경을 불가능하게 한다 . 2. 약속 = 다른 프로그래머와 제작자가 의도를 나눌 수 있다 .

const 의 위치와 의미 = 포인터 왼쪽 const 면 데이터를 상수화 , 포인터 오른쪽 const 면 포인터 자체를 상수화

Page 15: Effective c++chapter1 and2

STL 과 const 위치간의 관계

const STL::iterator 라면 const 가 포인터 오른쪽에 붙은 상황 , STL::const_iterator 라면 const 가 포인터 왼쪽에 붙은 상황이다

Item 3 : 낌새만 보이면 const 를 들이대 보자

Page 16: Effective c++chapter1 and2

함수 선언과 const 위치간의 관계

1. 함수 반환 값에 붙는 경우

함수 계산 후 나오는 값이 const

2. const 를 매개변수와 함께 사용하는 경우 지역 변수와 const 를 같이 쓰는 경우와 같다 .

Item 3 : 낌새만 보이면 const 를 들이대 보자

Page 17: Effective c++chapter1 and2

함수 선언과 const 위치간의 관계

3. const 가 멤버 함수 괄호 뒤에 붙는 경우

물리적 상수성을 지켜준다 = 클래스의 데이터 멤버 변수를 하나도 바꾸지 않는다

논리적 상수성을 지켜준다 = const 함수 안에서 객체의 데이터를 바꿀 수 있게 하되

사용자 측에서 알아차리지 못하게 하면 상수 멤버 자격이 있 게 하자

Item 3 : 낌새만 보이면 const 를 들이대 보자

Page 18: Effective c++chapter1 and2

논리적 상수성 ( 이어서 )

mutable 선언은 논리적 상수성이 가능하도록 합니다

Item 3 : 낌새만 보이면 const 를 들이대 보자

Page 19: Effective c++chapter1 and2

Item 4

객체를 사용하기 전에 반드시 그 객체를 초기화 하자

Page 20: Effective c++chapter1 and2

제목이 내용이다 . 사용하기 전에 모든 객체를 초기화 하자 .

그런데 대입을 초기화와 헷갈리지 말자

어떤 클래스의 생성자에서 대입 (=) 을 하는 것은 초기화가 아니다 .

// 대입 // 초기화 ( 초기화 리스트 사용 )

Item 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화 하자

Page 21: Effective c++chapter1 and2

제목이 내용이다 . 사용하기 전에 모든 객체를 초기화 하자 .

그런데 대입을 초기화와 헷갈리지 말자

어떤 클래스의 생성자에서 대입 (=) 을 하는 것은 초기화가 아니다 .

대입 ( 기본생성자 + 복사대입연산자 ) 초기화 ( 생성자 한번 )

Item 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화 하자

Page 22: Effective c++chapter1 and2

초기화 순서 : 기본 클래스는 파생 클래스보다 먼저 초기화 된다 클래스 데이터 멤버는 선언된 순서대로 초기화 된다

☞ 비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다

정적 객체란 ? 1. 전역 객체2. 네임스페이스 유효범위에서 정의된 객체3. 클래스 안에서 static 으로 정의된 객체4. 함수 안에서 static 으로 정의된 객체 ( 얘만 지역 정적 객체이다 )5. 파일 유효범위 안에서 stati 으로 정의된 객체 번역 단위란 ?- object file 을 만드는 바탕이 되는 소스코드 , 소스 파일 하나 + #include 코드

내용

결론 : 비지역정적 개체 초기화 순서는 알기 어렵다 .

Item 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화 하자

Page 23: Effective c++chapter1 and2

Chapter 2

생성자 , 소멸자그리고 대입 연산자

Page 24: Effective c++chapter1 and2

Item 5

C++ 가 은근슬쩍 만들어 호출해 버리는 함수들을 주의하자

Page 25: Effective c++chapter1 and2

C++ 컴파일러가 사용자가 선언해 놓지 않았지만 필요한 경우 만드는 함수 - 생성자 , 복사 생성자 , 복사대입 연산자 , 소멸자- 모두 public 이면서 inline 이다- 여기서 필요한 경우란 다음과 같은 상황이다

- 이렇게 만들어진 함수들은 어떤 일을 하는가 ? - 기본 생성자 , 기본 소멸자 -> “ 배후의 코드"를 깔 수 있는 자리 마련 , - 여기서 배후의 코드란 데이터 멤버의 기본 클래스 및 비정적 데이터 멤버의

생성자와 소멸자를 호출하는 코드등을 말한다- 복사 생성자 , 복사대입연산자 -> 원본 객체의 비정적 데이터 사본을 사본

객체쪽으로 복사- 예외 : 이미 어떤 생성자나 소멸자가 만들어져 있다면 컴파일러는 그에 해당하는

생성자나 소멸자를 스스로 만들지 않는다 . (중복을 걱정할 필요가 없다 .)

Item 5 : C++ 가 은근슬쩍 만들어 호출해 버리는 함수들을 주의하자

Page 26: Effective c++chapter1 and2

C++ 컴파일러가 복사 생성자 , 복사 대입 연산자를 만들지 않는 경우- 컴파일러는 똑똑해서 자신이 만들 복사 , 복사대입 생성자가 동작 후 최종 결과

코드가 ‘ 적법’하고 ‘이치에 닿는’ 코드가 아니라면 자동 생성을 거부한다- 예를 들어 Widget Class 에 다음과 같은 멤버 변수가 있다고 하자

- 생성자를 통해서 다음과 같은 2 개의 객체를 만들어 냈다고 하자

- 여기서 대입 연산을 한다면 ? ( 이어서 계속 )

Item 5 : C++ 가 은근슬쩍 만들어 호출해 버리는 함수들을 주의하자

Page 27: Effective c++chapter1 and2

( 이어서 )C++ 컴파일러가 복사 , 복사 대입 생성자를 만들지 않는 경우

- 먼저 w1 에 있는 name 이 참조이기 때문에 w2 에 있는 name 을 복사할 수 없다- 왜냐하면 c++ 에서 참조는 한 번 참조한 대상을 바꿀 수 없게 해놓았기 때문이다 - 따라서 대입연산이 애매하다

- 또 w1 의 value 에 w2 의 value 를 넣을 수도 없다- 왜냐하면 const 로 선언되어있기 때문이다 . - 이런 내용을 컴파일러는 알고 있기 때문에 자동으로 복사대입 연산을 만들지 않는다

- 정리하면 컴파일러는 ‘적합성’을 가지고 있고 ‘이치에 맞는’ 코드인지를 살필 수 있다- 그리고 맞지 않으면 해당 생성자를 만들지 않는다- 구체적으로 참조 멤버를 가지고 있다면 항상 복사대입연산자를 개발자 스스로

만들어야 한다

Item 5 : C++ 가 은근슬쩍 만들어 호출해 버리는 함수들을 주의하자

Page 28: Effective c++chapter1 and2

Item 6

컴파일러가 만들어낸 함수가 필요 없으면 사용을 못하게 하자

Page 29: Effective c++chapter1 and2

앞서 컴파일러가 개발자도 모르게 만들어내는 함수가 있음을 공부했다

- 그런데 컴파일러가 만들어내는 기본 함수들을 사용하고 싶지 않다면 ?- 그저 그 함수를 선언하지 않는 것으로 그만인가 ?- 아니다 . 컴파일러는 이내 선언 되지 않은 그 함수를 만들어낼 것이다- 이 문제의 해결 방법은 무엇일까 ?

1 번 : 함수를 private 로 선언하고 오직 선언만 한다 . no 정의

- 컴파일러가 만들어 내는 복사 , 복사 대입생성자는 public 이다- 우리는 명시적으로 복사 , 복사 대입생성자를 private 에서 선언할 수 있다- 이렇게 선언함으로써 외부로부터의 대입연산 호출을 막을 수 있다- 하지만 해당 클래스가 friend 선언에 의해 함수 호출을 막을 수 없는 상황이라면 ?- 이를 대비하기 위해 오직 헤더에서 선언만 하고 실제로 정의는 하지 않는 것이다- 이것이 가능한 이유는 누군가 해당 함수를 사용하려고 할 때 정의되지 않았다면

에러가 나기 때문이다

Item 6 : 컴파일러가 만들어낸 함수가 필요 없으면 사용을 못하게 하자

Page 30: Effective c++chapter1 and2

2 번 : 1 번과 같은 방식으로 한다 . 그런데 하나의 클래스를 그와 같이 만들고 이를 상속받아 사용 하게 한다

- 말은 어렵지만 구체적으로 보면 쉽다 . - 일단 class 를 하나 구현한다 . 이 class 는 오직 복사 방지만을 위해 존재한다

- 이제 이 class 를 사용하고자 하는 클래스가 상속 받자 - 그러면 해당 class 는 복사 , 복사대입 연산자를 사용할 수 없게 된다

Item 6 : 컴파일러가 만들어낸 함수가 필요 없으면 사용을 못하게 하자

Page 31: Effective c++chapter1 and2

Item 7

다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 !

Page 32: Effective c++chapter1 and2

Missile 을 상속 받는 LinearMissle 과 BulletMissile 의 Class 가 있다

- 사용자가 각 missile 에 접근하고자 할 때가 있다면 다음과 같은 함수를 만들 수 있다

- 이 함수는 파생클래스에 대한 기본 클래스의 포인터를 반환하는 함수이다- 실제로 이 함수를 사용한다면 missile 을 객체의 포인터를 얻을 수 있을 것이고

이제 그 미사일이 필요치 않다면 해당 내용을 지워야 할 것이다- 바로 아래처럼

Item 7 : 다형성을 가진 기본클래스에서는 소멸자를 반드시 가상 소멸자로 !

Page 33: Effective c++chapter1 and2

(Missile 계통 (Linear 나 Bullet 같은 ) 클래스로부터 동적 으로 할당된 객체의 포인터 )

- 이렇게 myMissile 을 지웠을 때 생길 수 있는 문제는 무엇인가 ?- 문제의 원인은 아래의 두 가지가 함께 나타나기 때문에 발생한다- 1 번 : 기본 클래스의 포인터를 가지고 있다 ( 내부에 할당된 것은 파생 클래스 )- 2 번 : 기본 클래스의 소멸자가 비가상 소멸자이다

- C++ 규정에 의하면 기본 클래스의 포인터를 통해 파생클래스가 삭제될 때 그 기본 클래스에 비가상 소멸자가 들어있으면 프로그램의 동작은 미정의 사항이다

- 보통은 파생클래스만 있는 부분이 삭제되지 않는다

- 이로 인해 자원이 낭비되고 , 디버깅은 어려워지며 , 짜증은 유발된다

Item 7 : 다형성을 가진 기본클래스에서는 소멸자를 반드시 가상 소멸자로 !

Page 34: Effective c++chapter1 and2

- 이 문제의 해결 방법 : 기본 클래스의 소멸자를 가상 소멸자로 선언 한다- 그러면 기본 클래스의 포인터를 들고 있을지라도 그 내용이 파생 클래스라면

파생 클래스의 소멸자라 불린다

- 우리는 이제 가상소멸자가 없는 클래스를 다음과 같이 생각할 수 있고 해야 한다- “이 클래스는 기본 클래스로 절대 쓰이지 않을 것입니다”

- 가상소멸자로 도배하는 것 혹은 전혀 사용하지 않는 것 모두 나쁜 습관이다- 기준을 잡자- 해당 클래스에 가상 함수가 하나라도 있다면 가상 소멸자를 선언한다

Item 7 : 다형성을 가진 기본클래스에서는 소멸자를 반드시 가상 소멸자로 !

Page 35: Effective c++chapter1 and2

주의 할 것- 어떤 기본 타입들은 가상 소멸자가 선언되어 있지 않다- 따라서 상속에 주의가 필요하다- 해당 타입은 std::string 이나 STL 컨테이너등이 그것이다- 위의 예처럼 가상소멸자가 선언되어 있지 않은 타입을 상속받아서 쓰는 일은

없어야 한다

마지막 포인트- 어떤 Class 를 추상 Class 로 만들고 싶다면 소멸자를 순수 가상 소멸자로

선언하자- 추상 Class 의 목적 = 기본 Class 로 사용 = 따라서 소멸자는 가상

소멸자여야 한다- 추상 Class 의 목적 + 기본 Class 의 목적을 동시에 만족시키는 순수 가상

소멸자 !!!

Item 7 : 다형성을 가진 기본클래스에서는 소멸자를 반드시 가상 소멸자로 !

Page 36: Effective c++chapter1 and2

Item 8

예외가 소멸자를 떠나지 못하도록 하자

Page 37: Effective c++chapter1 and2

필자의 걱정 : 소멸자 좋지 . 그런데 소멸자가 불려서 작동하는 동안 , 즉 그 Class가 없어질 때 오류가 발생하면 어쩌지 ? 이미 Class 도 사라져서 다시 호출할 수도 없는데 ? 그리고 C++ 은 소멸자에서 오류가 발생하면 그 예외가 던져지도록 (별다른 조치가 취해지지 않게 ) 놔두는데 ? 흐음

해결방식 1 : 소멸자 도중에 예외가 발생하면 프로그램을 끝내자 !단점 : 에러 발생시 프로그램을 종료시켜도 된다면 가능 , 하지만 프로그램을 계속 가동되게 해야 한다면 ?

Item 8 : 예외가 소멸자를 떠나지 못하도록 하자

Page 38: Effective c++chapter1 and2

해결방식 2 : 소멸자 도중에 예외가 발생하면 예외를 삼키자

단점 : 문제는 해결되지 않는다 . 이렇게 하면 이후의 실행이 보장되도록 해야 하는데 쉽지 않다

Item 8 : 예외가 소멸자를 떠나지 못하도록 하자

Page 39: Effective c++chapter1 and2

해결방식 3 : 소멸자에서 예외가 일어나서는 안된다 . 예외가 일어난다면 그건 다른 함수에서 이미 일어났어야 한다 . 해결방식 3 을 한마디로 정의하면 확인 사살이다

ex)WidgetManager 와 Widget Class 가 있다Widget Class 에서 close() 함수를 호출한다 . 그리고 close() 가 잘 적용 되었는지 bool 값을로 저장해 놓는다 .WidgetManager Class 에서는 소멸자에서 Widget Class 의 bool 값을 확인한다 . 만약 bool 값이 false 라면 다시 하번 close() 함수를 호출한다 .

만약 WidgetManager 에서도 오류가 발생하면 해결방식 1 이나 2 로 다시 가야 한다 . 하지만 이 방법은 적어도 2 번의 오류 체크를 하기 때문에 해결방식 1,2 보다는 더 나은 방식이라 할 수 있다 .

Item 8 : 예외가 소멸자를 떠나지 못하도록 하자

Page 40: Effective c++chapter1 and2

Item 9

객체의 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

Page 41: Effective c++chapter1 and2

객체 생성 및 소멸 과정 중에 절대로 가상 함수를 호출하면 안 되는 이유

1. 호출 결과가 원하는 대로 돌아가지 않을 것이다

2. 이번에 원하는 대로 돌아간다고 해도 다음 번에도 원하는 대로 돌아간다는 보장은 없기 때문이다 .

Item 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

Page 42: Effective c++chapter1 and2

- Missile Class 와 이를 상속 받은 파생 Class 인 LinearMissile 이 있다- LinearMissile 의 생성자에서 sayMyType 을 호출하면 어떤 일이 일어날까 ?

- 사실 화면에 찍히길 바라는 것은 “ LinearMissile” 이다- 하지만 정작 찍히는 내용은 “Missile” 이다- 왜 이런 일이 일어날까 ?- 원칙은 파생 Class 에서 기본 Class 부분이 생성되고 있을 때 파생 Class 의 가상

함수가 호출 되지 않는다는 것이다 .

Item 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

Page 43: Effective c++chapter1 and2

- 이유는 파생 Class 가 생성될 때 먼저 기본 Class 생성자가 실행 된다- 이 때 가상 함수가 호출 된다면 파생 Class 의 멤버 변수는 초기화 되어 있지 않다- 따라서 초기화 되어 있지 않은 파생 Class 의 멤버 변수를 건드리고 있을 수도 있는

파생 Class 의 가상 함수는 호출 되지 않는 것이다- 만약 건드린다면 이는 프로그램을 미정의 동작으로 몰아넣는 일이 될 것이다

핵심 : 파생 Class 의 기본 Class 부분이 생성되는 동안 파생 Class 객체의 타입은 기본 Class 타입이다

핵심 2 : 파생 Class 객체의 소멸자가 불리면 그 즉시 파생 Class 는 기본 Class Type 으로 취 급 된다 . 기본 Class 의 소멸자가 호출되면 그 때는 그냥 기본 Class Type 이다

핵심 3 : 생성자와 소멸자에서는 어떤 함수가 내부적으로 가상 함수를 불러와서는 안된다

Item 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

Page 44: Effective c++chapter1 and2

Item 9 의 문제를 해결하는 코드

- 기본 Class 인 Missile 에 sayMyType 을 선언하고 Missile 정보를 매개변수로 설정한다

- 아이템의 type 이름을 함수에서 보여준다- 이렇게 하면 비가상 함수이기 때문에 생성자에서 문제가 생기지 않는다

Item 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

Page 45: Effective c++chapter1 and2

Item 10

대입 연산자는 *this 를 반환하게 하자

Page 46: Effective c++chapter1 and2

C++ 대입 연산은 사슬처럼 엮일 수 있습니다(ex. x = y = z = 15)

다른 특징은 우측 연관 연산인데 위의 예가 다음과 같이 계산 된다x = (y = (z = 15))

이렇게 좌측으로 좌측으로 대입이 진행 되기 위해서는 대입 연산자가 좌측 타입의 참조자를

반환해야 한다

이러한 C++ 스타일을 지켜주기 위해서 우리가 어떤 Class 에 대입연산자를 선언 했다면

`ClassType& 를 반환하자 ` 가 이번 Item 에서 하고자 하는 말이다

Item 10 : 대입 연산자는 *this 를 반환하게 하자

Page 47: Effective c++chapter1 and2

대입 연산자 구현 코드

선언

구현

기본 대입 연산자뿐만 아니라 operator+= 이나 -=, *= 도 이런 식으로 구현 해주자

Item 10 : 대입 연산자는 *this 를 반환하게 하자

Page 48: Effective c++chapter1 and2

Item 11

operator= 에서 자기 대입에 대한 처리가 빠지지 않도록 하자

Page 49: Effective c++chapter1 and2

Item 11 : operator= 에서 자기 대입에 대한 처리가 빠지지 않도록 하자자기 대입 = 어떤 객체가 자기 자신에 대해 대입연산자를 적용 하는 것

너무 대놓고 써서 이런 일이 안 벌어질 것 같지만 사실 벌어지기 쉽다 // 여기서 i 와 j 가 같다면 ?

// 여기서 pM1 과 pM2 가 가리키는 대상이 같았다면 ?

Page 50: Effective c++chapter1 and2

Item 11 : operator= 에서 자기 대입에 대한 처리가 빠지지 않도록 하자operator= 구현을 살펴보면서 이야기를 진행하도록 하자

delete 지점이 자기 대입 연산에서 문제가 되는 이유는 무엇인가 ?

- 만약 rhs 와 this 가 같은 것을 가리키고 있다면- m_W 를 delete 하는 시점에 rhs 의 멤버 m_W 가 가진 내용도 delete 된다

Page 51: Effective c++chapter1 and2

Item 11 : operator= 에서 자기 대입에 대한 처리가 빠지지 않도록 하자대책 1 : 일치성 검사

- this 와 일치하는지 살피고 바로 *this 를 리턴 하는 방법

단점 : 일치성 검사를 통과 했다고 해도 new 에서 문제가 발생한다면 ?( 메모리 부족이라든가 )

m_W 는 뭔가 문제가 있는 녀석의 pointer 가 된다 . 짜증나는 디버깅의 시작

Page 52: Effective c++chapter1 and2

Item 11 : operator= 에서 자기 대입에 대한 처리가 빠지지 않도록 하자대책 2 : 포인터가 가리키는 객체를 복사한 후 삭제

- new 에서 문제가 발생하더라도 m_W 가 가리키고 있던 내용은 유지 되어 있으므로( 복사를 통해서 ) delete 시 문제가 발생하지 않음

Page 53: Effective c++chapter1 and2

Item 11 : operator= 에서 자기 대입에 대한 처리가 빠지지 않도록 하자대책 3-1 : copy and swap

- tmp 로 rhs 를 복사한다음 swap 하는 형식인데 자세한 내용은 item 29 에서 다룬다

- 여기에서는 어떻게 구현하는지만 알아 도록 한다

Page 54: Effective c++chapter1 and2

Item 11 : operator= 에서 자기 대입에 대한 처리가 빠지지 않도록 하자대책 3-2 : copy and swap

- 대책 3-1 과 다른 점은 값에 의한 전달이 이루어진다는 것이다- 이도 역시 차후에 자세히 다루도록 하고 여기서는 구현 방식만 알아두기로 하자

Page 55: Effective c++chapter1 and2

Item 12

객체의 모든 부분을 빠짐없이 복사하자

Page 56: Effective c++chapter1 and2

Item 12 : 객체의 모든 부분을 빠짐 없이 복사하자

Item 5 에서 다루었던 것처럼 때에 따라서 컴파일러는 복사 생성자나 복사 대입 생성자를 스스로 만들기도 한다 . 이 때 컴파일러는 다음의 대원칙을 지킨다

` 복사 되는 객체가 가지고 있는 데이터를 빠짐없이 복사 한다 `

우리가 복사 생성자나 복사 대입 연산자를 만든다는 것은 컴파일러가 만들어 주는 복사 , 복사 대입 생성자의 어느 부분이 마음에 들지 않아서이다 . 그러면 컴파일러도 우리를 마음에 들어 하지 않는다 . 우리가 잘못된 복사 생성자나 복사 대입 연산자를 만들어 거의 확실히 틀린 경우에도 컴파일러는 이를 잡아주지 않는다

그래서 우리는 멤버 변수를 하나 추가할 때마다 복사 생성자 , 복사 대입 연산자에도 해당 멤버를 복사하는 내용을 추가해줘야 한다

Page 57: Effective c++chapter1 and2

Item 12 : 객체의 모든 부분을 빠짐 없이 복사하자

새롭게 생긴 멤버 변수들의 대입 부분을 복사 생성자 , 복사 대입 연산자에 빠짐 없이 넣었다고 생각 해도 완전하게 만든 것은 아니다 . 상속과 관련된 부분에서 문제가 생길 수 있는데 다음 코드를 살펴 보자

Missile Class 를 상속 받은 LinearMissile 의 복사 생성자 , 복사 대입 연산자이다문제가 없어 보입니다만 ?문제는 상속 받은 기본 Class 가 갖는 데이터에 대한 복사가 이루어지지 않았다는 것이다

Page 58: Effective c++chapter1 and2

Item 12 : 객체의 모든 부분을 빠짐 없이 복사하자

( 이어서 )이렇게 되면 복사 생성자를 마치면 LinearMissile 객체가 갖는 Missile Class 의 데이터 부분은 기본 값으로 초기화 된다

복사 대입 생성자는 그렇지 않은데 왜냐하면 복사 대입 생성자는 기본 Class 데이터를 건드리지 않기 때문이다 ( 초기화 시도를 하지 않는다 ). 따라서 기존에 저장된 데이터들이 남아 있게 된다 . 이 또한 rhs 의 데이터를 다 받아오는 것이 아니기 때문에 의도한 대로 대입이 이루어지고 있는 것이 아니다

이 문제를 해결하기 위해서 코드에 다음 페이지와 같은 내용을 추가 하도록 하자

Page 59: Effective c++chapter1 and2

Item 12 : 객체의 모든 부분을 빠짐 없이 복사하자

기본 클래스의 복사 생성자 , 복사 대입 연산자를 초기화 리스트에서 호출

이로써 상속 관계에서도 우리가 원했던 데이터의 빠짐없는 복사 할 수 있게 됨