이펙티브 C++
1. C++에 왔으면 C++의 법을 따릅시다.	

2. 생성자, 소멸자 및 대입 연산자
131066	

정문철
C++를 언어들의 연합체로 바라보는 안목은 필수
C++은 단순히 C언어에 객체 지향 기능 몇 가지가 결합된 형태	

!
계속 발전되고 여러가지 프로그래밍 개념이 추가됨	

!
- 여러 언어의 연합체
C - C++의 기초가 되는 언어	

!
객체 지향 개념의 C++ - 클래스(생성자, 소멸자 개념까지), 캡슐화, 상속,
다형성, 가상함수 등 객체 지향의 개념 추가	

!
템플릿 C++ - C++의 일반화 프로그래밍 부분	

!
STL - 템플릿 라이브러리
C++은 하위 언어들로 구성되어 있고	

각각의 하위 언어가 자신만의 규칙을 갖고 있다.
#define을 쓰려거든 const, enum, inline을 떠올리자
define은 컴파일러가 확인하기 전에 처리되어 버린다.	

이 때문에 몇가지 문제가 발생할 수 있으니 가능하면	

컴파일러를 믿고 컴파일러에게 일을 맡기자.
#define ASPECT_RATIO 1.653	

이런식으로 쓰면 컴파일 에러가 났을때 ASPECT_RATIO라고	

표시 되 질 않고 3.14라고 표시가 나서 에러 찾기 힘듬	

!
-상수를 사용하자	

const double AspectRatio = 1.653;	

!
!
+ define을 쓰면 코드안에 1.653이 등장 횟수만큼 생성되지만	

상수를 쓰면 여러 번 사용되더라도 한 개만 생기는 경우가	

발생 할 수 있다.
상수 포인터(constant pointer)를 정의할 때에는 주의하자.	

!
상수 정의는 대개 헤더 파일에 넣게 되므로 포인터는 const로 선언하고	

포인터가 가르키는 대상까지 const로 선언을 해줘야 한다	

(2번 const사용)	

!
const char* const authorName = “Scott Meyers”;	

!
클래스 멤버로 상수를 사용할 때 사본 개수가 한개로 만들고 싶으면	

static을 사용한다.
주소가 필요하거나 컴파일러가 문제를 일으키면	

(클래스 상수의 초기값이 해당 상수가 선언된 시점에서 바로 주어지기 때문…)	

헤더에 선언만 하고 구현 파일에서 정의를 하자.
클래스를 컴파일 할 때 클래스 상수의 값이 필요할 때에는	

(배열 초기화 - 배열의 크기 필요)	

!
enum을 사용하자.
enum을 쓰면 좋은점	

!
-다른 사람이 주소를 얻는다든지 참조자를 쓰는것을 막을 수 있다.	

-#define처럼 쓸데없는 메모리 할당을 하지 않는다.	

-템플릿 메타 프로그래밍에서 많이 사용된다.
매크로를 만들 때에는 #define이 아니라 inline을 사용하자.	

!
define을 사용한 기존 매크로의 효율을 그대호 유지하면서	

정규 함수의 모든 동작방식 및 타입 안정성까지 취할 수 있다.
낌새만 보이면 const를 들이대 보자
외부 변경을 불가능하게 하는 제약 사항을 만들어	

제작자의 의도를 컴파일러 및 다른 프로그래머와 나눌 수 있다.
const가 * 왼쪽에 있으면 포인터가 가리키는 대상이 상수고	

오른쪽에 있으면 포인터가 상수	

!
const가 * 양쪽에 있으면	

가리키는 대상 및 포인터가 다 상수
매개 변수의 왼쪽 오른쪽은 관계없음
함수 선언부에 const를 쓰면 함수 전체에 대해	

const의 성질을 붙일 수 있다.
tb는 그냥 로컬 변수고 ctb가 const면	

ctb의 값을 변경하지 못한다.
const여도 포인터로 값 변경이 가능하다	

일부러 값 변경을 위해 mutable
객체를 사용하기 전에 반드시 그 객체를 초기화 하자
직접 초기화를 하지 않으면 자동으로	

초기화 된다는 것을 보장하지 못한다.	

!
초기화가 안된 값을 읽기만 해도 프로그램이 멈출 수 있으니	

꼭 초기화를 하자.	

!
물론 규칙은 있지만 그냥 초기화 하자
생성자에 초기화는 대입= 이 아니라 ()으로 하자
사실 비용은 상관없다…
초기화 순서1
!
- 파생 클래스보다 기본 클래스가 먼저 초기화 된다.	

!
- 클래스 데이터 멤버는 선언된 순서대로 초기화 된다(위에서 아래로)
초기화 결론
!
꼭 초기화 해야되는 경우와 안해도 되는 경우를	

외우지 말고 그냥 초기화 해주자
객체를 사용하기 전에 반드시 그 객체를 초기화 하자
직접 초기화를 하지 않으면 자동으로	

초기화 된다는 것을 보장하지 못한다.	

!
초기화가 안된 값을 읽기만 해도 프로그램이 멈출 수 있으니	

꼭 초기화를 하자.	

!
물론 규칙은 있지만 그냥 초기화 하자
생성자, 소멸자 및 대입 연산자
거의 모든 C++ 클래스에는 생성자, 소멸자, 대입 연산자가 있다.	

이를 잘 활용해 보자.
C++이 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
!
!
빈 클래스를 만들어도 생성자, 대입 연산자,	

소멸자는 자동으로 만들어서 호출한다.
컴파일러가 만들어낸 함수가 필요 없으면
확실히 이들의 사용을 금해 버리자
!
!
클래스의 복사를 막고 싶으면 private로 복사 대입 연산자로 만들자	

!
-friend클래스도 막고 싶으면 정의하지 말고	

헤더에 선언만 하자
다형성을 가진 기본 클래스에서는
소멸자를 반드시 가상 소멸자로 선언하자
!
상속을 받고	

가상 함수가 하나라도 있는 곳에서	

delete를 할 때에는 소멸자에 delete를 붙여주자.
추상 클래스를 만들고 싶은 클래스에	

순수 가상 소멸자를 선언하자
가상 소멸자 결론
!
-다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언하자	

(가상 함수를 하나라도 갖고 있으면 가상 소멸자여야 한다)	

!
- 기본 클래스로 설계되지 않았거나 다형성을 갖고 있지 않으면	

가상 소멸자를 선언하지 말아야 한다.
예외가 소멸자를 떠나지 못하도록 붙들어 놓자
!
소멸자 안에서 예외가 발생하는 바람에 삭제 되지 못하고	

남아버려서 메모리 누수가 발생하는 것을 방지하자
프로그램을 바로 종료시켜버리자.
예외 삼키기 - 로그를 남긴뒤에 계속 진행	

(예외를 그냥 무시한 뒤라도 프로그램이 신뢰성 있게 실행을 지속할 수 있어야 합니다.
-소멸자에서는 예외가 빠져나가면 안된다.	

소멸자 안에서 예외가 나올 수 있으면 소멸자안에서 잘 처리하던지	

프로그램을 종료해라.	

!
-연산중의 예외에 사용자가 반응해야 한다면	

보통의 함수로 만들자(소멸자X)
객체 생성 및 소멸 과정 중에는
절대로 가상 함수를 호출하지 말자 붙들어 놓자
!
발판을 밟고 있는 상태에서 발의 발판을 빼는 느낌이랄까	

!
가상함수가 실행되지 않고 그냥 자신의 함수가 실행이된다.	

왜냐면 아직 가상함수 쪽은 초기화가 되지도 않은상태다.	

!
소멸도 마찬가지	

컴파일러가 안된다고 해주는 경우도 있다.
결론
!
생성자 혹은 소멸자 안에서 가상 함수를 호출하지 말자	

어차피 파생 클래스쪽으로 내려가지 않는다.
대입 연산자는 *this의 참조자를 반환하게 하자
대입 연산은 우측 연관으로 들어감	

대입 연산이 사슬처럼 엮이려면 대입 연산자가	

좌변 인자에 대한 참조자를 반환하도록 구현	

!
이런 구현은 일종의 관례인데 그 관례를 지키도록 하자
*this를 반환하자
대입 operator=에서는
자기대입에 대한 처리가빠지지 않도록하자
w = w같이 자기에게 자신을 넣는것은 정상 코드임	

이렇게 중복 참조를 할 때 자기 대임에 대해 안전하게 동작해야 함
일치성 검사를 해보았는데…	

!
(하지만 포인터가 삭제된 주소를 계속 갖고 있을수 있음)
포인터가 가르키는 객체를 복사한 후에 삭제를 하자.
결론
!
operator=을 구현할 때에는 자신이 대입되는 경우를 처리하자	

주소를 비교해도 되고, 순서를 조정하거나 복사 후 맞바꾸기를 하자.	

!
두개 이상의 객체에 대해 동작하는 함수가 있으면	

함수에 넘겨지는 객체가 같은 객체일때 정확하게 동작하는지 확인하자
객체의 모든 부분을 빠짐없이 복사하자
복사 함수(operator=)를 직접 구현할 때에는	

컴파일러가 잘못을 지적을 안하는 경우가 많으니 주의해서 작성하자
name은 복사되지만 lastTransaction은 복사안되는	

부분 복사가 된다.	

!
(이런건 컴파일러가 지적을 안해준다.)	

직접 복사 함수를 다시 작성을 해주자
클래스의 복사 함수 두 개를 구현할 때에는 헬이 터진다.	

!
한쪽을 이용해서 다른 쪽을 구현하려고 하지 말고	

공통된 동작은 3의 함수에다 분리하고	

양쪽에서 이것을 호출하게 만들어서 해결하자.

1 2 1

  • 1.
    이펙티브 C++ 1. C++에왔으면 C++의 법을 따릅시다. 2. 생성자, 소멸자 및 대입 연산자 131066 정문철
  • 2.
    C++를 언어들의 연합체로바라보는 안목은 필수 C++은 단순히 C언어에 객체 지향 기능 몇 가지가 결합된 형태 ! 계속 발전되고 여러가지 프로그래밍 개념이 추가됨 ! - 여러 언어의 연합체
  • 3.
    C - C++의기초가 되는 언어 ! 객체 지향 개념의 C++ - 클래스(생성자, 소멸자 개념까지), 캡슐화, 상속, 다형성, 가상함수 등 객체 지향의 개념 추가 ! 템플릿 C++ - C++의 일반화 프로그래밍 부분 ! STL - 템플릿 라이브러리 C++은 하위 언어들로 구성되어 있고 각각의 하위 언어가 자신만의 규칙을 갖고 있다.
  • 4.
    #define을 쓰려거든 const,enum, inline을 떠올리자 define은 컴파일러가 확인하기 전에 처리되어 버린다. 이 때문에 몇가지 문제가 발생할 수 있으니 가능하면 컴파일러를 믿고 컴파일러에게 일을 맡기자.
  • 5.
    #define ASPECT_RATIO 1.653 이런식으로쓰면 컴파일 에러가 났을때 ASPECT_RATIO라고 표시 되 질 않고 3.14라고 표시가 나서 에러 찾기 힘듬 ! -상수를 사용하자 const double AspectRatio = 1.653; ! ! + define을 쓰면 코드안에 1.653이 등장 횟수만큼 생성되지만 상수를 쓰면 여러 번 사용되더라도 한 개만 생기는 경우가 발생 할 수 있다.
  • 6.
    상수 포인터(constant pointer)를정의할 때에는 주의하자. ! 상수 정의는 대개 헤더 파일에 넣게 되므로 포인터는 const로 선언하고 포인터가 가르키는 대상까지 const로 선언을 해줘야 한다 (2번 const사용) ! const char* const authorName = “Scott Meyers”; ! 클래스 멤버로 상수를 사용할 때 사본 개수가 한개로 만들고 싶으면 static을 사용한다.
  • 7.
    주소가 필요하거나 컴파일러가문제를 일으키면 (클래스 상수의 초기값이 해당 상수가 선언된 시점에서 바로 주어지기 때문…) 헤더에 선언만 하고 구현 파일에서 정의를 하자.
  • 8.
    클래스를 컴파일 할때 클래스 상수의 값이 필요할 때에는 (배열 초기화 - 배열의 크기 필요) ! enum을 사용하자.
  • 9.
    enum을 쓰면 좋은점 ! -다른사람이 주소를 얻는다든지 참조자를 쓰는것을 막을 수 있다. -#define처럼 쓸데없는 메모리 할당을 하지 않는다. -템플릿 메타 프로그래밍에서 많이 사용된다.
  • 10.
    매크로를 만들 때에는#define이 아니라 inline을 사용하자. ! define을 사용한 기존 매크로의 효율을 그대호 유지하면서 정규 함수의 모든 동작방식 및 타입 안정성까지 취할 수 있다.
  • 11.
    낌새만 보이면 const를들이대 보자 외부 변경을 불가능하게 하는 제약 사항을 만들어 제작자의 의도를 컴파일러 및 다른 프로그래머와 나눌 수 있다.
  • 12.
    const가 * 왼쪽에있으면 포인터가 가리키는 대상이 상수고 오른쪽에 있으면 포인터가 상수 ! const가 * 양쪽에 있으면 가리키는 대상 및 포인터가 다 상수
  • 13.
    매개 변수의 왼쪽오른쪽은 관계없음
  • 14.
    함수 선언부에 const를쓰면 함수 전체에 대해 const의 성질을 붙일 수 있다. tb는 그냥 로컬 변수고 ctb가 const면 ctb의 값을 변경하지 못한다.
  • 15.
    const여도 포인터로 값변경이 가능하다 일부러 값 변경을 위해 mutable
  • 16.
    객체를 사용하기 전에반드시 그 객체를 초기화 하자 직접 초기화를 하지 않으면 자동으로 초기화 된다는 것을 보장하지 못한다. ! 초기화가 안된 값을 읽기만 해도 프로그램이 멈출 수 있으니 꼭 초기화를 하자. ! 물론 규칙은 있지만 그냥 초기화 하자
  • 17.
    생성자에 초기화는 대입=이 아니라 ()으로 하자 사실 비용은 상관없다…
  • 18.
    초기화 순서1 ! - 파생클래스보다 기본 클래스가 먼저 초기화 된다. ! - 클래스 데이터 멤버는 선언된 순서대로 초기화 된다(위에서 아래로)
  • 19.
    초기화 결론 ! 꼭 초기화해야되는 경우와 안해도 되는 경우를 외우지 말고 그냥 초기화 해주자
  • 20.
    객체를 사용하기 전에반드시 그 객체를 초기화 하자 직접 초기화를 하지 않으면 자동으로 초기화 된다는 것을 보장하지 못한다. ! 초기화가 안된 값을 읽기만 해도 프로그램이 멈출 수 있으니 꼭 초기화를 하자. ! 물론 규칙은 있지만 그냥 초기화 하자
  • 21.
    생성자, 소멸자 및대입 연산자 거의 모든 C++ 클래스에는 생성자, 소멸자, 대입 연산자가 있다. 이를 잘 활용해 보자.
  • 22.
    C++이 은근슬쩍 만들어호출해 버리는 함수들에 촉각을 세우자 ! ! 빈 클래스를 만들어도 생성자, 대입 연산자, 소멸자는 자동으로 만들어서 호출한다.
  • 23.
    컴파일러가 만들어낸 함수가필요 없으면 확실히 이들의 사용을 금해 버리자 ! ! 클래스의 복사를 막고 싶으면 private로 복사 대입 연산자로 만들자 ! -friend클래스도 막고 싶으면 정의하지 말고 헤더에 선언만 하자
  • 24.
    다형성을 가진 기본클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 ! 상속을 받고 가상 함수가 하나라도 있는 곳에서 delete를 할 때에는 소멸자에 delete를 붙여주자.
  • 25.
    추상 클래스를 만들고싶은 클래스에 순수 가상 소멸자를 선언하자
  • 26.
    가상 소멸자 결론 ! -다형성을가진 기본 클래스에는 반드시 가상 소멸자를 선언하자 (가상 함수를 하나라도 갖고 있으면 가상 소멸자여야 한다) ! - 기본 클래스로 설계되지 않았거나 다형성을 갖고 있지 않으면 가상 소멸자를 선언하지 말아야 한다.
  • 27.
    예외가 소멸자를 떠나지못하도록 붙들어 놓자 ! 소멸자 안에서 예외가 발생하는 바람에 삭제 되지 못하고 남아버려서 메모리 누수가 발생하는 것을 방지하자
  • 28.
  • 29.
    예외 삼키기 -로그를 남긴뒤에 계속 진행 (예외를 그냥 무시한 뒤라도 프로그램이 신뢰성 있게 실행을 지속할 수 있어야 합니다.
  • 30.
    -소멸자에서는 예외가 빠져나가면안된다. 소멸자 안에서 예외가 나올 수 있으면 소멸자안에서 잘 처리하던지 프로그램을 종료해라. ! -연산중의 예외에 사용자가 반응해야 한다면 보통의 함수로 만들자(소멸자X)
  • 31.
    객체 생성 및소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 붙들어 놓자 ! 발판을 밟고 있는 상태에서 발의 발판을 빼는 느낌이랄까 ! 가상함수가 실행되지 않고 그냥 자신의 함수가 실행이된다. 왜냐면 아직 가상함수 쪽은 초기화가 되지도 않은상태다. ! 소멸도 마찬가지 컴파일러가 안된다고 해주는 경우도 있다.
  • 32.
    결론 ! 생성자 혹은 소멸자안에서 가상 함수를 호출하지 말자 어차피 파생 클래스쪽으로 내려가지 않는다.
  • 33.
    대입 연산자는 *this의참조자를 반환하게 하자 대입 연산은 우측 연관으로 들어감 대입 연산이 사슬처럼 엮이려면 대입 연산자가 좌변 인자에 대한 참조자를 반환하도록 구현 ! 이런 구현은 일종의 관례인데 그 관례를 지키도록 하자
  • 34.
  • 35.
    대입 operator=에서는 자기대입에 대한처리가빠지지 않도록하자 w = w같이 자기에게 자신을 넣는것은 정상 코드임 이렇게 중복 참조를 할 때 자기 대임에 대해 안전하게 동작해야 함
  • 36.
    일치성 검사를 해보았는데… ! (하지만포인터가 삭제된 주소를 계속 갖고 있을수 있음)
  • 37.
    포인터가 가르키는 객체를복사한 후에 삭제를 하자.
  • 38.
    결론 ! operator=을 구현할 때에는자신이 대입되는 경우를 처리하자 주소를 비교해도 되고, 순서를 조정하거나 복사 후 맞바꾸기를 하자. ! 두개 이상의 객체에 대해 동작하는 함수가 있으면 함수에 넘겨지는 객체가 같은 객체일때 정확하게 동작하는지 확인하자
  • 39.
    객체의 모든 부분을빠짐없이 복사하자 복사 함수(operator=)를 직접 구현할 때에는 컴파일러가 잘못을 지적을 안하는 경우가 많으니 주의해서 작성하자
  • 40.
    name은 복사되지만 lastTransaction은복사안되는 부분 복사가 된다. ! (이런건 컴파일러가 지적을 안해준다.) 직접 복사 함수를 다시 작성을 해주자
  • 41.
    클래스의 복사 함수두 개를 구현할 때에는 헬이 터진다. ! 한쪽을 이용해서 다른 쪽을 구현하려고 하지 말고 공통된 동작은 3의 함수에다 분리하고 양쪽에서 이것을 호출하게 만들어서 해결하자.