27
Effective C++ Chapter1. C++ 에 에에에 C++ 에 에에 에에에에 . Chapter2. 에에에 , 에에에 에 에에 에에에 NHNNEXT 2 기 141078 기기기

Effective c++ 1,2

  • Upload
    -

  • View
    184

  • Download
    3

Embed Size (px)

Citation preview

Page 1: Effective c++ 1,2

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

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

NHNNEXT 2 기 141078 정세빈

Page 2: Effective c++ 1,2

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

• Item1. C++ 를 언어들의 연합체로 바라보는 안목은 필수

• Item2. #define 을 쓰려거든 const, enum, inline 을 떠올리자

• Item3. 낌새만 보이면 const 를 들이대 보자

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

Page 3: Effective c++ 1,2

Item1. C++ 를 언어들의 연합체로 바라보는 안목은 필수

• C++ 는 다중패러다임 프로그래밍 언어= 여러 하위 언어들의 연합체

• 여러 하위 언어 ?

절차적 프로그래밍이 기본 : C 언어객체 지향 프로그래밍 : 클래스 , 캡슐화 , 상속 , 다형성 , 가상 함수함수식 프로그래밍 : FC++ 라이브러리일반화 프로그래밍 : 템플릿 , STL 메타 프로그래밍 : TMP( 템플릿 메타 프로그래밍 )

이것들의 연합체• 그래서 ?

위의 개념을 생각하면 , C++ 가 가지고 있는 여러 언어들의 특징을 때에 맞춰 사용하여 효율적 프로그래밍을 할 수 있다 .

Page 4: Effective c++ 1,2

Item2. #define 을 쓰려거든 const, enum, inline 을 떠올리자

• 상수를 선언할 때

의 문제

선행 처리자가 숫자 상수로 바꾸어 버리기 때문에 컴파일러에겐 ASPECT_RATIO라는 기호가 넘어가지 않는다 .

컴파일 에러가 났을 때 ASPECT_RATIO 가 아닌 1.653 으로 표시해주기 때문에 헷갈릴 수 있음디버그 시에도 기호 테이블에 들어가있지 않기 때문에 기호로 확인이 불가하다 .

• 위 현상을 해결하기 위해 const 로 상수 만들기

const 로 만든 상수는 기호 테이블에 들어갈 뿐더러 사본은 한번만 생성되기 때문에 #define보다 크기도 작을 수 있다 .

Page 5: Effective c++ 1,2

• 클래스 멤버로 상수를 정의할 때

#define 으로는 클래스 상수를 정의할 수도 없을 뿐더러 캡슐화의 혜택도 받을 수 없다 .

• 이번에도 const 를 쓰면 ?

클래스 상수로 쓰일 수 있다 . 물론 캡슐화의 기능도 사용 가능하다 . 단 , 선언과 정의를 헤더에서 동시에 할 수는 없다 .

Item2. #define 을 쓰려거든 const, enum, inline 을 떠올리자

Page 6: Effective c++ 1,2

• enum 은 언제 ?

특별한 경우 ( 선언과 동시에 그 클래스 상수를 사용해야 하여 정의도 미리 되어있어야 하는 경우 ) 가 존재한다 .

이 때 , enum 을 사용하여 선언과 정의를 헤더에서 동시에 하면 된다 .

• enum 은 그럴때만 ?

enum 으로 선언한 정수의 주소를 얻는 것을 막아 보안성을 높여주고 , 쓸데없는 메모리 할당을 줄여준다 .

또 , 실용적인 이유에서 코드에 자주 쓰이니 익숙해지는게 좋다 .

Item2. #define 을 쓰려거든 const, enum, inline 을 떠올리자

Page 7: Effective c++ 1,2

• 매크로 함수를 정의할 때

결과에 따라 달라지는 함수 호출이 문제가 된다 .

• 이 때 inline 을 이용하자

정규 함수의 기본 방식과 타입의 안정성까지 보장해준다 .

Item2. #define 을 쓰려거든 const, enum, inline 을 떠올리자

Page 8: Effective c++ 1,2

• const 는 일반적으로 변경이 불가한 상수로 취급할 때 쓰임

• 함수 반환값에는 항상 const 를 !! 안정성과 효율을 증가시키면서 에러도 줄일 수 있다 .

• operator 의 반환값에도 항상 const 를 !!operator 의 반환값에 const 를 써주면

와 같은 어이없는 실수에 에러를 호출할수 있다 .

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

Page 9: Effective c++ 1,2

• 멤버 함수에서의 const “ 상수 객체에 대해 호출될 함수이다 . "를 알려주는 역할

• 이것의 장점 인터페이스의 원활함 : 해당 클래스로 만들어진 객체를 변경할 수 있는 혹은 없는 함수는 무엇인지 사용자측으로 알려준다 .

상수 객체에 대한 참조자를 넘김으로써 해당 객체를 조작할 수 있게 한다 .

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

Page 10: Effective c++ 1,2

• C++ 의 C 부분만을 사용하면 값이 초기화 된다는 보장이 없다 .

• C++ 의 STL 부분을 사용하면 그러한 보장을 해준다 .

가장 좋은 방법은 모든 객체를 사용 하기 전에 항상 초기화 !

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

Page 11: Effective c++ 1,2

• 생성자에서의 대입

대입으로 원하는 값으로 시작할 순 있다 . ( 가짜 초기화 )

그러나 C++ 의 규칙에 의하면 객체는 데이터 멤버의 생성자의 본문이 호출 되기 전에 초기화 되어야 한다 .

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

Page 12: Effective c++ 1,2

• 생성자에서의 초기화

|

위와 같은 것을 멤버 초기화 리스트라고 한다 .

초기화 리스트를 사용하면 , 사용된 인자들이 데이터 멤버에 대한 생성자의 인자로 사용되기 때문에 바로 초기화가 가능하다 .

괄호 안에 아무 인자도 넣어 주지 않아도 자동으로 각 타입의 기본값으로 초기화를 진행한다 .

초기화 순서는 데이터 멤버의 선언 순서에 영향을 받는다 .

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

Page 13: Effective c++ 1,2

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

- 비지역 정적 객체 : 전역 객체, 네임스페이스에 있는 객체, 클래스 혹은 파일에 있는 정적 객체

- 번역 단위 : 기본적으로 소스파일 하나 ( 해당 소스파일에 들어있는 헤더파일도 포함 )

번역단위가 다르면 비지역 정적 객체의 초기화 순서는 알 수가 없다 .

번역 단위가 다른 소스에서 초기화 되지도 않은 멤버를 가져다가 사용하는 에러가 생길 수 있다 .

해결 방법 : 비지역 정적 객체를 직접 가져다 쓰는 것을 방지하고 함수를 통해 해당 멤버의 참조자를 가져다 쓰게 하면 된다 .

비지역 정적 객체를 지역 정적 객체로 바꾸는 것

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

Page 14: Effective c++ 1,2

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

• Item5. c++ 가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자• Item6. 컴파일러가 만들어낸 함수가 필요 없으면

확실히 이들의 사용을 금해버리자 • Item7. 다형성을 가진 기본 클래스에서는

소멸자를 반드시 가상 소멸자로 선언하자• Item8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자• Item9. 객체 생성 및 소멸 과정 중에는

절대로 가상 함수를 호출하지 말자• Item10. 대입 연산자는 *this 의 참조자를 반환하게 하자• Item11. operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자• Item12. 객체의 모든 부분을 빠짐없이 복사하자 .

Page 15: Effective c++ 1,2

• 어떤 멤버는 클래스 안에 선언 되어 있지 않으면 컴파일러가 기본멤버로 자동 생성한다 . 복사 생성자 , 복사 대입 연산자 , 소멸자 ( 물론 생성자도 선언이 되어있지 않다면 동일취급 )

• 문제는 참조자 or 포인터를 복사하려 할 때:C++ 의 참조자는 기존에 참조하고 있던 것과 다른 객체를 함께 참조 할 수 없기 때문에

이를 막기 위해 복사 생성자 , 복사 대입 연산자를 직접 정의 하는게 좋다 . ( 컴파일러가 기본으로 생성하여 생기는 오류를 막기 위해 )

Item5. c++ 가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자

Page 16: Effective c++ 1,2

• Item5 에서 말했듯이 클래스에 선언되지 않은 몇몇 멤버를 컴파일러가 생성하는 경우가 있다 .

• 애초에 컴파일러가 생성할 필요가 없고 , 이것을 막고 싶다면

클래스의 private 으로 복사 생성자와 복사 대입 연산자를 선언하고 구현부를 비워두면 복사 생성자 , 복사 대입 연산자의 사용을 막을 수 있다 .

Item6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자

Page 17: Effective c++ 1,2

• 파생 클래스를 가지고 있는 기본 클래스의 소멸자가 비 가상 소멸자이면 , 대개 그 객체의 파생 클래스는 소멸되지 않는 참사가 벌어진다 .

해결법은 간단하다 . 기본 클래스의 소멸자앞에 virtual 키워드만 붙여주면 된다 .( 내가 알기론 파생 클래스들의 소멸자에도 virtual 키워드를 붙여야 하는걸로 안다 .)

• 파생 클래스가 없는 기본 클래스의 소멸자에 무작정 virtual 키워드를 붙이는건 에러를 호출 하진 않지만 , vptr(virtual table pointer) 가 늘어나면서 파일의 크기를 늘린다 . 좋지 않은 행위

Item7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자

Page 18: Effective c++ 1,2

• 순수 가상 함수를 가진 추상 클래스에서는순수 가상 소멸자를 두면 편하다 .

단 , 순수 가상 소멸자의 정의를 본문에서 안해주면링커 에러를 호출할 수도 있으니 꼭 정의해줄것

Item7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자

Page 19: Effective c++ 1,2

• 소멸자에서 예외가 발생하면 ?

예외 처리하는 도중 또 다른 소멸이 이루어 지고 또 예외가 발생한다면 예외가 겹치면서 C++ 에 과부화가 발생한다 .

이 경우 ( 소멸자에서 예외가 겹치는 경우 ) 에는 프로그램이 정의 되지 않은 동작을 보인다 . 에러

Item8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자

Page 20: Effective c++ 1,2

• 소멸자에 close 선언

close 가 되면 아무 문제 없지만 close 에서 예외가 나온다면 또 다시 같은 문제가 재발하는 것

• try ~ catch 로 close 감싸기

1. close 에서 예외 발생시 std::abort() 로 프로그램 종료

2. 예외를 삼켜버리기 = catch 문에 아무것도 하지 않음

둘다 그다지 좋지 않은 방법그렇다면 ???

Item8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자

Page 21: Effective c++ 1,2

• close() 따로 선언하기

사용자가 사용할 수 있는 close() 를 만들어 사용자가 직접 소멸자를 호출하게 한다 .

예외 발생시의 오류는 사용자 탓이므로 괜찮다 !!

Item8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자

Page 22: Effective c++ 1,2

• 생성자에서 가상 함수는 금물

기본 클래스 생성자는 파생 클래스 생성자보다 앞서서 진행 되는데 , 만약 기본 클래스 생성자 진행중에 나온가상 함수가 파생 클래스에 접근하게 되면 아직 초기화 되지 않은 대상을 접근 하였으므로 오류를 범하게 된다 .

• 소멸자에서도 금물

파생 클래스의 소멸자가 호출 되고 나면 C++ 는 해당 클래스에 들어있던 가상 함수를 없는 코드 취급하게 된다 .만약 , 기본 클래스가 이 상황에서 파생 클래스의 함수에 접근 하게 된다면 이도 마찬가지 오류를 범하게 될 것이다 .

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

Page 23: Effective c++ 1,2

Item10. 대입 연산자는 *this 의 참조자를 반환하게 하자

• C++ 의 대입연산은 사슬처럼 엮일 수 있다 .

해석하면

• 위와 같은 코드를 다른 객체에도 적용 시키려면

operator= 의 정의에 return 을 *this 로 하여 참조자를 반환하게 하면 된다 .

(+=, -=, *= 도 마찬가지 )

Page 24: Effective c++ 1,2

• 자기대입 : 어떤 객체가 자기 자신에 대해 대입 연산자를 적용 하는것

자기 대입 가능성이 가득한 코드

• 같은 객체가 사용될 가능성 ( 중복참조 ) 를 고려해야한다 .

*this 와 rhs 가 같은 객체일 가능성이 있다 . 그렇게 되면 삭제된 객체를 return 해버리는 불상사가 발생한다 .

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

Page 25: Effective c++ 1,2

• operator= 앞머리에서 일치성 검사를 통해 중복 참조를 검사를 하면 된다 .

• 하지만 new Bitmap() 에서 예외가 난다면 ?

기존의 멤버가 삭제가 되어 삭제된 포인터만 가지고 있게 된다 .

해결법 : 기존의 멤버를 복사하여 임시 저장한 후에 new Bitmap() 을 하고 , 잘 되었으면 복사했던 포인터를 삭제하여 기존의 멤버를

삭제한다 .

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

Page 26: Effective c++ 1,2

• 객체의 변수가 한 개라도 복사가 안되면 부분복사가 일어나게 된다 .

이 때 , 컴파일러는 아무 말도 해주지 않아서 문제

고로 우리는 멤버 변수 하나하나 복사할 복사 함수를 만들어 주어야 한다 .

• 만약 파생 클래스를 복사할 때 , 기본 클래스에 들어있는 멤버는 ??

그렇다 , 예상대로 컴파일러의 기본 복사 함수는 이것도 검토해주지 않는다 . 파생 클래스의 복사 함수를 만들 때 기본 클래스의 복사 함수를 호출하는 구조로 만들어 주어야 한다 .

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

Page 27: Effective c++ 1,2

• 복사 대입 연산자에서 복사 생성자를 호출하는 것은 금물 !!

기존의 멤버가 복사된 상태로 들어가 데이터의 손상가능성이 있다 . 그 반대 ( 복사 생성자에서 복사 대입 연산자 호출 ) 도 마찬가지로 위험

• 복사 대입 연산자 , 복사 생성자의 다른 방법

겹치는 멤버들을 대상으로 묶어서 별도의 멤버 함수를 만드는 것

대체적으로 private 에 선언하여 쓰고 init() 이라는 이름으로 자주 쓰임

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