'C++'에 해당되는 글 1건
- 2009.10.26 Effective C++ 요약
Effective C++
Scott Meyers 저
연승훈, 표광준 역
Eclipse(김주현) 정리
※ C에서 C++로의 전환
1. #define 보다는 const와 inline을 사용한다.
2. 보다는 을 사용한다.
3. malloc과 free보다는 new와 delete를 사용한다.
4. C++ 스타일의 주석을 지향한다.
※ 메모리관리
5. new와 delete의 사용시 동일한 형식을 이용한다.
6. 소멸자에서 포인터 맴버에 대해 delete를 이용한다.
7. 메모리가 모자랄 경우에 대비한다.
8. operator new와 delete 작성시 관례를 따른다.(skip)
9. new의 “정상”형식을 감추지 않는다.(skip)
10. operator new를 작성한다면 operator delete도 작성한다.(skip)
※ 생성자, 소멸자, 그리고 치환 연산자
11. 동적으로 할당되는 메모리를 갖는 클래스를 위해서는 복사 생성자와 치환 연산자를 선언하라.
12. 생성자에서 치환보다는 초기화를 사용한다.
13. 클래스에 선언된 순서에 따라 멤버 초기화 리스트에 멤버들을 나열한다.
14. 부모 클래스에서 소멸자는 가상 함수로 선언해야 한다.
15. operator=이 this*에 대한 레퍼런스를 리턴하도록 한다.(skip)
16. operator=에서 모든 데이터 멤버들로의 치환을 수행한다.(skip)
17. operator=에서 재귀치환을 검사한다.
※ 클래스와 함수: 설계 및 선언
18. 최소한의 완전한 클래스 인터페이스를 구축한다.
19. 멤버 함수, 비멤버 함수 및 프렌드 함수를 구별한다.(skip)
20. 데이터 멤버를 공용 인터페이스에 포함시키지 않는다.(skip)
21. 가능한 const를 상용한다.
22. 값에 의한 호출보다는 레퍼런스에 의한 호출을 한다.
23. 객체 반환시 레퍼런스를 반환하지 않는다.
24. 함수 오버로딩과 디폴트 인자값 중에서 주의깊게 선택한다.(skip)
25. 포인터나 수치형 타입상의 오버로딩을 피한다.
26. 잠재적 모호성을 경계한다.
27. 의도하지 않은 내부 생성 멤버 함수의 이용을 명시적으로 막는다.(skip)
28. 전역 네임스페이스를 분할한다.
※ 클래스와 함수 : 구현
29. 내부 데이터에 대한 “핸들”을 리턴하는 것을 피해라.
30. 접근하기 어려운 멤버에 대한 비상수 포인터나 레퍼런스를 리턴하는 멤버 함수 사용을 피하라.(29.와 이유가 같음)
31. 지역객체에 대한 참조나 함수 내에서 new를 이용해 초기화된 포인터를 가리키는 참조를 리턴하지 말라.
32. 변수 정의는 가능한 뒤로 늦춰라.
33. 인라인을 선별적으로 사용하라.
34. 파일간의 컴파일 의존성(dependency)을 최소화하라.
※ 인스턴스와 객체지향 설계
35. public 계승이 “is a(‘~는 ~이다’)관계”를 모델링하도록 하라.(skip)
36. 인터페이스 계승과 구현 계승의 차이점을 이해하라.(skip)
37. 계승된 비가상 함수를 재정의하지 않도록 한다.
38. 계승된 부재 인자값(Default agument)을 재정의하지 않도록 한다.
39. 계층도의 아래쪽 클래스로 다운캐스트(down cast)하지 않도록 한다.
40. 레이어링(layering)을 통해 “가지고 있는” 것과 “사용하여 구현된” 것을 모델링하도록 한다.
41. 계승과 템플릿과의 차이점을 이해한다.(skip)
42. private 계층을 바르게 사용하라.
43. 다중 계승을 바르게 사용하도록 하라.
44. 의마하는 바를 표현하도록하라; 자신이 표현한 것의 의미를 이해하도록 하라.
※ 미묘한 부분
45. C++가 은밀하게 어떤 함수를 만들어주고 호출하는지 이해하기
46. 실행시간 에러보다는 컴파일 시간 과 링크 시간 에러가 좋다.
47. 비지역 정적(Non-local static) 객체는 사용되기전에 초기화되도록 해야 한다.
48. 컴파일러의 경고(warning)에 주의를 기울여라.
49. 표준 라이브러리를 잘 알아두어라.
50. C++에 대한 이해를 넓혀라.(skip)
※ C에서 C++로의 전환
1. #define 보다는 const와 inline을 사용한다.
전처리기보다는 컴파을러를 선호한다. 전처리기에서 처리되는 내용은 디버깅시, 컴파일러가 알고있는 내용이 아니기 때문에 전처리기를 이용한 심볼이 들어간 자리에서 에러가 나게 되면 알아차리기 어렵다.
전처리기를 이용한 메크로의 사용은(Ex. #define max(a, b) ? ((a) : (b)) ) 올바르게 사용하였을 경우에도 오류(Ex. Max(++a, b); 의 경우 a의 값이 두번 증가하게 된다.)가 생길 가능성이 있다. 이 경우는 inline함수를 이용해서 사용하는 것이 좀 더 효율적이다.
(Ex. Inline int max(int a, int b) { return a > b ? a : b; } )
2. stdio.h 보다는 iostream을 사용한다.
Scanf()와 printf()는 이식성이 좋고, 효율적이지만, 확장성이 없다.
Ex.
scanf(“%d %s”&a,c)
cin >> a >> c;
printf(“%d %s”, a, c);
cout << a << c;
3. malloc과 free보다는 new와 delete를 사용한다.
Malloc과 free(혹은 그들의 변종)의 문제점은 생성자와 소멸자의 존재를 모른다는 것이다.
Ex.
String *stringArray1 = static_cast(malloc(sizeof(string)*10));
String *stringArray2 = new string[10];
위의 경우 stringArray1는 string의 10개의 공간을 확보하지만, 어떠한 생성도 없다. 하지만 stringArray2의 경우 string의 생성자를 호출하게 된다.
마찬가지로 free의 경우 소멸자를 호출하지 않고, delete의 경우 소멸자를 호출하기 때문에 유용하다 할 수 있다.
4. C++ 스타일의 주석을 지향한다.
아래와 같은 기본 소스가 있다고 가정하자.
if(a < b)
{
Int temp = a; /*a와 b를 스왑*/
a = b;
b = temp;
}
이때 전체를 주석처리하고자 하게 되면
if(a < b)
{
/*Int temp = a; /*a와 b를 스왑*/
a = b;
b = temp;
*/
}
가 된다. 이 경우 주석이 ‘왑*/’에서 끝나게 되므로 뒷부분은 그냥 수행되게 된다.
※ 메모리관리
5. new와 delete의 사용시 동일한 형식을 이용한다.
string *stringArray = new string[100];
…
delete stringArray;
여기서 잘못된 부분은 delete시 하나의 메모리 공간만 삭제하게 되는 것이다. 100개의 공간을 할당했지만, 삭제시에는 배열임을 알려주지 않았기 때문에 단순히 첫번째 배열포인터의 공간만 제거하게 된다. 위의 코드를 올바르게 수정하면 아래와 같다.
string *stringArray = new string[100];
…
delete [] stringArray;
6. 소멸자에서 포인터 맴버에 대해 delete를 이용한다.
기본적인 클래스 혹은 메인 루틴에서의 동작은 아래와 같이 한다.
(1) 생성자 각각에서 포인터의 초기화. 만일 특정 생성자에서 메모리가 포인터에 할당되지 않을 것이라면 그 포인터는 널포인터로 초기화되어야 한다.
(2) 할당 연산제어서 기존 메모리의 삭제와 새로운 메모리의 할당
(3) 소멸자에서 포인터의 삭제
7. 메모리가 모자랄 경우에 대비한다.
operator new가 요구된 메모리를 할당할 수 없을 때 예외를 발생시킨다.(예전의 컴파일러는 0을 반환했다고 한다.) 메모리가 모자라는 예외 상황을 처리하는 것은 순전히 윤리적 행동이라고 볼 수 있으며 동시에 그렇게 하는 것이 고통을 수반한다는 것을 깨닫게 될 수 있다. (Eclipse의 개인적인 생각에는 서버어플리케이션이 아닌경우에, 이런 처리를 한다는 것은 개발비용 낭비라고 생각한다. 하드웨어 자원이 남아도는 이 시대에 이런 처리는 시간낭비가 아닐까?)
대비하기 위한 방법으로는 아래와 같다.
(1) 좀더 많은 메모리를 확보한다.
(2) 다른 new 헨들러를 설치한다.
(3) 기존의 new 헨들러를 무용지물로 만든다.
(4) std::bad_alloc 타입 또는 std::bad_alloc으로부터 유도된 다른 타입의 예외를 던진다.
(5) Abort나 exit를 호출함으로써 리턴하지 않는다.
8. operator new와 delete 작성시 관례를 따른다.(skip)
9. new의 “정상”형식을 감추지 않는다.(skip)
10. operator new를 작성한다면 operator delete도 작성한다.(skip)
※ 생성자, 소멸자, 그리고 치환 연산자
11. 동적으로 할당되는 메모리를 갖는 클래스를 위해서는 복사 생성자와 치환 연산자를 선언하라.
동적으로 할당되는 메모리를 갖는 클래스(이하 A)가 있다고 가정하자. 아래와 같은 코드에서
A a = new A();
A b = a;
a의 동적인 요소c는 b에 복사되는 것이 아니라 그 포인터만 복사하게 된다. 이에 a.c와같은 메모리 공간을 가르키게 되고, 따라서 b.c가 변하게 되면 a.c의 동적인 요소도 함께 변하게 된다.
따라서 복사생성자, 치환연산자를 선언해주어야 위와 같은 문제가 발생하지 않는다. 다만 위의 경우를 염려해둔 경우라면 크게 필요는 없을 것이겠지만, 되도록이면 복사생성자, 치환연산자를 만들어 두도록하는 것이 좋다.
12. 생성자에서 치환보다는 초기화를 사용한다.
생성자에서의 치환의 경우 const나 레퍼런스 멤버를 가지고 있지 않다. 그렇더라도, 생성자 내에서 치환연산을 하는 것보다 맴버 초기화 리스트를 사용하는 것이 더 바람직한다.
멤버 초기화 리스트를 사용하는 것은 효율성면에서도 유리하다. 멤버초기화 리스트가 사용될 때, 하나의 string멤버 함수가 호출된다. 생성자 내에서 치환 연산을 하는 경우에는 두 번 호출된다.
객체의 생성은 두 단계를 거친다.
(1) 데이터 멤버의 초기화
(2) 호출된 생성자의 코드가 실행된다.
이때 기본 생성자에 대한 호출을 하고 나머지 한번은 생성자에서 치환연산을 하게된다. 따라서 생성자에서의 치환연산을 하게되면 두단계를 거치게 된다.
13. 클래스에 선언된 순서에 따라 멤버 초기화 리스트에 멤버들을 나열한다.
모든 객체에 대해서 생성자와 소멸자의 호출 순서는 동일하게 되어 있고, 초기화 리스트에 나열된 순서는 무시된다. 정적 데이터 멤버들의 경우 전역적이고 이름을 갖는 객체로 동작하기 때문에 한번 초기화된다. 베이스 클래스 데이터 멤버들이 계승된 클래스의 데이터 멤버들보다 먼저 초기화된다. 그러므로 상속을 한 경우, 멤버 초기화 리스트의 제일 앞에서 베이스 클래스를 초기화해주어야 한다.
14. 부모 클래스에서 소멸자는 가상 함수로 선언해야 한다.
부모 클래스에 대한 포인터를 사용해서 계승된 클래스를 삭제하려고 하며, 부모 클래스는 가상 소멸자를 가지고 있지 않은 경우, 그 결과는 정의되어있지 않다. 이것은 자식 클래스의 소멸자가 호출되지 않는다는 것이다.
하지만 가상 함수로 선언을 할 경우 쉐도잉에 의해 부모 클래스의 소멸자는 가려지고, 자식 클래스의 소멸자가 호출되게 되는 것이다.
15. operator=이 this*에 대한 레퍼런스를 리턴하도록 한다.(skip)
16. operator=에서 모든 데이터 멤버들로의 치환을 수행한다.(skip)
17. operator=에서 재귀치환을 검사한다.
재귀치환은 다음과 같은 경우에 일어난다.
class X{};
X a;
a = a; //재귀치환이 일어남
X b = a;
a = b; //재귀치환이 일어남
치환연산자가 수행하는 첫 번째 작업이 data를 삭제하는 것이며, 그 결과 삭제된 data를 다시 결과로 가져가는 경우가 생기게 된다. 따라서 재귀치환을 검사해야 한다.
※ 클래스와 함수: 설계 및 선언
* 클래스 정의시 유의 사항
1) 객체들이 어떻게 생성되고 소멸될 것인가?
: 이것이 얼마나 강력하게 수행되느냐하는 것은 생성자와 소멸자의 설계에 영향을 미친다.
2) 객체 초기화와 객체 치환이 어느 정도 차이가 나는가?
: 이 질문에 대한 해답은 생성자와 치환 연산자의 동작 및 차이점을 결정한다.
3) 새로운 타입의 객체를 값에 의해 전달하는 것은 무엇을 의미하는가?
: 복사 생성자는 객체를 값으로 전달하는 것이 의미하는 바를 정의한다는 것을 기억한다.
4) 새로운 타입상에 적법한 값을 위한 제약점은 무엇인가?
: 이 제약사항은 멤버 합수, 특히 생성자와 치환 연산자 내부에서 수행할 에러 검사의 종류를 결정한다. 그것은 또한 함수의 예외사항을 이용할 경우 함수가 제기하는 예외사항들에 영향을 미칠 수도 있다.
5) 새로운 타입이 상속 그래프에 맞는가?
: 만일 기존 클래스로부터 상속된다면 그 클래스의 설계에 의해, 특히 상속한 함수가 가상(virtual)인지의 여부에 의해 제한을 받는다.
6) 어떤 종류의 타입 변환이 허용되는가?
: 만일 타입 A의 객체가 내부적으로 타입B의 객체로 변환되는 것을 허용하기를 원한다면 클래스 A에서 타입 변환 함수를 작성하거나 클래스 B에서 단일 인자로 호출될 수 있는 내부적인 생성자를 작성해야 할 것이다. 만일 명시적 변환만을 허용하기 원한다면 변환을 위한 수행하기 위한 함수들을 작성해야 하며 그들을 타입 변환 연산자나 내부적인 단일 인자 생성자로 만드는 것을 피해야 한다.
7) 어던 연산자와 함수가 새로운 타입을 위해 적당한가?
: 이 질문에 대한 대답은 클래스 인터페이스에서 어떤 함수들을 선언할 것인가를 결정한다.
8) 어떤 표준 연산자와 함수들이 명시적으로 허용되지 말아야 하는가?
: 그러한 것들은 private로 선언될 필요가 있는 것이다.
9) 누가 새로운 타입의 멤버에 접근하여야 하는가?
: 이 질문은 어떤 멤버들이 public 또는 private인지를 결정하는데 큰 도움을 준다.
10) 새로운 타입은 얼마나 일반적인가?
:실제 새로운 타입을 정의하지 않고 모든 계열의 타입들을 정의하고 있다면 새로운 클래스를 정의할 것이 아니라 새로운 클래스 템플릿을 정의하여야 한다.
18. 최소한의 완전한 클래스 인터페이스를 구축한다.
어떤 함수들이 클래스 인터페이스 내에 있어야 하는지를 구분한다는 것은 어려운 질문이며 완전히 다른 두 가지 방향이 존재한다. 하나는 이해하기 쉽고 직관적이며 구현하기 쉬운 클래스를 만드는 것이다. 이것은 보통 적은 개수의 멤버 함수들을 의미한다. 그들은 명확한 작업을 수행한다. 반면에 강력하고 사용하기 편리한 클래스를 만들 수 있다. 이것은 일반적으로 수행되는 작업들을 지원하기 위한 함수들을 추가하는 것을 말한다.
최소한의 완전한 클래스 인터페이스 : 클라이언트들이 합리적으로 원할 수 있는 것들을 수행하게 하는 인터페이스를 말한다.
19. 멤버 함수, 비멤버 함수 및 프렌드 함수를 구별한다.(skip)
20. 데이터 멤버를 공용 인터페이스에 포함시키지 않는다.(skip)
21. 가능한 const를 상용한다.
const의 장점은 “특정 객체는 수정되지 말아야 한다.”는 것과, 같은 어떤 의미적 제한을 가할 수 있다는 것이며 컴파일러는 그 제한이 이행되도록 돕는다. Const는 컴파일러와 프로그래머 모두에게 이 값은 변경되지 말아야 한다고 말하는 것과 같다.
22. 값에 의한 호출보다는 레퍼런스에 의한 호출을 한다.
C에서는 모든 것이 값에 의해 전달(pass-by-value)되며, C++에서도 이를 수용하여 값에 의한 전달을 디폴트로 채택하고 있다. 따로 지정하지 않는 이상 함수 인자들은 실인자(actual argument)의 복사본으로 초기화되며 함수 호출자는 함수에 의해 반환된 값의 복사본을 돌려 받는다. 이러한 값에 의한 전달 방식은 인자의 크기가 커지게 되면 메모리를 그만큼 낭비하게 된다. 따라서 되도록이면 값에 의한 호출보다 레퍼런스에 의한 호출을 하도록하는 것이 좋다.
23. 객체 반환시 레퍼런스를 반환하지 않는다.
객체를 반환할 경우 누가 new에 의해 생성된 객체를 delete할 것인가에 대한 문제가 생긴다. 이는 바로 메모리 누수로 직결되기 마련이다. 호출자가 함수 결과의 주소를 취하고 그에 대한 delete를 수행한다고 하더라도 복잡한 양식들은 프로그래머가 결코 미칠 수 없는 이름없는 임시적인 것들을 양산해낼 것이다.
24. 함수 오버로딩과 디폴트 인자값 중에서 주의깊게 선택한다.(skip)
25. 포인터나 수치형 타입상의 오버로딩을 피한다.
void f(int x);
void f(string *ps);
f(0);
위의 경우 컴파일러는 f(int x)를 호출한다. 하지만 사용자는 언제나 그것을 원하는 것은 아니다. 0은 NULL도 의미하기 때문이다.(이때 string*를 호출하려면 0대신 NULL을 입력해두는 것이 좋다.) 또 아래의 경우는 언제나 int형을 호출하게 된다. 따라서 되도록이면 수치형 타입의 오버로딩은 피하는 것이 좋다.(불가피한 경우 타입케스팅을 하는 것이 좋겠다.)
void f(int x);
void f(long int a);
void f(double b);
f(0);
26. 잠재적 모호성을 경계한다.
void f(int a);
void f(char b);
double d = 6.02;
f(d); //애매모호함
다중상속은 잠재적 모호성을 가질 가능성이 높다. 가장 간단한 경우로서 자식 클래스가 한개 이상의 부모클래스로부터 같은 이름의 맴버를 상속받았을 경우이다. 이 경우 ‘해당 부모클래스명::호출할멤버’로 호출을 하면된다. 하지만 이 때 ‘부모클래스명::’을 명시해주지 않을 경우 에러가 나게 된다.
27. 의도하지 않은 내부 생성 멤버 함수의 이용을 명시적으로 막는다.(skip)
28. 전역 네임스페이스를 분할한다.
네임스페이스에 관해서 좋은 점들 중의 한가지는 잠재적 모호성이 에러가 아니라는 것이다. 심볼을 네임스페이스단위로 분할 해놓기 때문에 충돌을 막을 수 있다.
※ 클래스와 함수 : 구현
29. 내부 데이터에 대한 “핸들”을 리턴하는 것을 피해라.
내부의 데이터에 대한 포인터를 리턴하게 되면 const_cast를 이용하여 const속성을 제거 할 수 있게 된다. 따라서 변경하지 못하게 막아놓았던 부분들을 외부로 노출하게 된다.
30. 접근하기 어려운 멤버에 대한 비상수 포인터나 레퍼런스를 리턴하는 멤버 함수 사용을 피하라.(29.와 이유가 같음)
31. 지역객체에 대한 참조나 함수 내에서 new를 이용해 초기화된 포인터를 가리키는 참조를 리턴하지 말라.
지역 객체에 대한 참조를 리턴하는 부분에서의 문제는 지역 객체는 지역적이라는 것이다. 이는 지역 객체는 정의되는 순간에 만들어지고 스코프를 벗어나면 제거된다는 것이다. 즉, 함수가 리턴을 하면 지역 객체는 삭제된다.
32. 변수 정의는 가능한 뒤로 늦춰라.
C에서 변수정의는 블록의 시작부분에 한다. C++에서 그러한 것은 불필요하고 부자연스러우며 비용이 많이 든다.
33. 인라인을 선별적으로 사용하라.
인라인 함수의 원리는 모든 함수 호출을 함수 본체로 바꿔버리는 것이다. 이는 오브젝트 코드의 크기를 증가시키는 원인이 된다. 따라서 제한된 메모리상에서 돌아가는 프로그램의 경우 인라인 함수를 자제하는 것이 좋다.(MS컴파일러는 성능이 무진장 똑똑해서 인라인을 표시하지 않아도 성능이 올라갈 것이라 예측되면 자동으로 인라인으로 변경하기 때문에 아니다 싶으면 그냥 인라인을 제거하고 선언하는 것이 좋을 듯 하다.)
34. 파일간의 컴파일 의존성(dependency)을 최소화하라.
프로그램을 하다보면 클래스 구현의 일부를 변경해야 할 경우가 있다. 이 경우 변경된 클래스만의 파일이 아니라 이 클래스를 참조하거나 사용하는 모든 클래스의 파일을 재컴파일해줘야한다. 또한 클래스의 구조중 일부가 변경되게 되면 이를 수정하기 위해서 많은 노력이 들어가게 된다.
※ 인스턴스와 객체지향 설계
35. public 계승이 “is a(‘~는 ~이다’)관계”를 모델링하도록 하라.(skip)
36. 인터페이스 계승과 구현 계승의 차이점을 이해하라.(skip)
37. 계승된 비가상 함수를 재정의하지 않도록 한다.
class B{
Public :
void mf();
};
Class D : public B{ … }; //mf()를 재정의했다고 가정.
일때
B *pB = new D();
D *pD = new D();
pB->mf(); //B::mf()
pD->mf(); //D::mf();
로 서로 다른 동작을 보인다.
38. 계승된 부재 인자값(Default agument)을 재정의하지 않도록 한다.
가상함수는 동적으로 결합되지만, 부재 인자는 정적으로 결합된다. 따라서 static으로 선언을 했을 경우, 문제가 발생할 수 있다.
39. 계층도의 아래쪽 클래스로 다운캐스트(down cast)하지 않도록 한다.
경우에 따라 다르겠지만, 아래쪽 클레스에서 선언한 함수는 상위 클레스로 선언한 객체에서는 작동하지 않게 된다.
40. 레이어링(layering)을 통해 “가지고 있는” 것과 “사용하여 구현된” 것을 모델링하도록 한다.
레이어링은 “has a(가지고 있는 것)” 또는 “사용해서 구현된 것”을 의미한다. 만일 클래스 A가 B타입의 테이터 멤버를 포함하고 있다면, A타입의 객체는 B타입의 요소를 가지고 있거나 B타입의 객체를 사용해서 구현된 것이다.
41. 계승과 템플릿과의 차이점을 이해한다.(skip)
42. private 계층을 바르게 사용하라.
private로 상속을 받은 경우, is a 관계가 되지 않는다.
(Ex. 사람을 학생이 private로 상속받은 경우, 학생은 사람이다가 성립 안함.)
private로 상속 규칙
1) 컴파일러는 자식 클래스 객체를 부모 클레스 객체로 변환(casting)하지 않는다.
2) private로 상속을 받게 되면 부모 클래스로부터 상속받은 멤버변수 및 멤버함수는 자식 클래스의 private 멤버가 된다.(public -> private, protected -> private, private -> private)
43. 다중 계승을 바르게 사용하도록 하라.
class Lottery{
public:
virtual int draw();
};
class GraphicalObject{
public:
virtual int draw();
};
class LotterrySimul: public Lottery,
public GraphicalObject{
//draw를 선언하지 않는다.
}
class SpecialLottery: public LotterrySimul {
virtual int draw();
};
LotterrySimul *pls = new LotterrySimul
pls = new SpecialLottery;
pls->draw(); //에러(SpecialLottery의 draw()호출 불가
//(다운 케스트(down cast)가 되지 않기 때문)
pls->Lottery::draw(); // Lottery의 draw()호출
pls->GraphicalObject::draw(); // GraphicalObject의 draw();호출
44. 의마하는 바를 표현하도록하라; 자신이 표현한 것의 의미를 이해하도록 하라.
공통 베이스 클레스는 공통 특징들을 의미한다. 만일 클래스 D1과 클래스 D2가 클래스 B를 공통으로 사용했다면, D1과 D2는 B로부터 동일한 데이터 멤버 그리고(또는) 동일한 멤버 함수들을 계승한다.
public 계승은 “is a(~은 ~이다.)”관계를 의미한다. 만일 클래스 D가 B로부터 public으로 계승되었다면, 모든 D타입(형식)의 객체들은 B타입의 객체이지만, 그 역은 아니다.
private 계승은 “using(사용해서 구현된 것)”을 의미한다. 만일 클래스 D가 클래스 B로 부터 private 계승되었다면, D 타입의 객체는 B타입의 객체를 통해 구현되었다는 것을 나타낸다. Bㄴ타입의 객체와 D타입의 객체간에 어떤 개념적인 간계도 존재하지 않는 것이다.
레이어링은 “has a(가지고 있는 것)” 또는 “사용해서 구현된 것”을 의미한다. 만일 클래스 A가 B타입의 테이터 멤버를 포함하고 있다면, A타입의 객체는 B타입의 요소를 가지고 있거나 B타입의 객체를 사용해서 구현된 것이다.
※ 미묘한 부분
45. C++가 은밀하게 어떤 함수를 만들어주고 호출하는지 이해하기
빈클레스를 생성했을 경우 컴파일러에서 자동으로 생성자, 할당 생성자, 소멸자, 조소 변환 연산자를 호출해주게 된다.
class Empty{
public:
Empty(); //디폴트 생성자
Empty(const Empty& rhs); //복사 생성자
~Empty(); //소멸자
Empty& operator = (const Empty& rhs); //할당연산자
Empty* operator(); //주소 변환 연산자
const Empty* operator&() const;
};
46. 실행시간 에러보다는 컴파일 시간 과 링크 시간 에러가 좋다.
C나 C++의 경우 컴파일 끝난 시점에서 컴파일러는 에러에 대한 신경을 쓰지 않는다. 프로그램이 컴파일러와 링커를 벗어나면 홀로 서야 한다. 인터프리터 언어의 경우 실행시간동안 오류를 잡아내는 능력이 있지만 컴파일랭귀지의 경우 이에 해당하지 않는다.
날짜를 파라메터로 갖는 클래스의 경우 클래스 생성시 파라메터에 대한 유효성 검사가 없다면 에러를 발생할 수도 있는 것이다.
경계 검사를 수행하는 배열을 구현하는 클래스를 만들어 배열이 접근될 때마다 배열 인덱스와 경계값을 비교하도록한다. 이렇게 하는 것에 대한 보상은 프로그램이 빨라지고 작아지고 더욱 믿을 수 있어진다는 것이다.
47. 비지역 정적(Non-local static) 객체는 사용되기전에 초기화되도록 해야 한다.
* 비지역적 객체
- 전역이나 네임스페이스 스코프에서 정의된다
- 클래스 내에 static으로 선언된 경우
- 파일 스코프에서 static으로 정의된 경우
다른 모듈에 있는 비지역 정적 객체의 초기화 순서는 제어할 방법이 없기 때문에 이로 인해 소프트웨어의 동작이 영향받지 않길 원한다면 사용되기전에 초기화하는 것이 좋다.
싱글레톤(Singleton Pattern)은 먼저 각각의 정적 객체를 함수를 하나 만들어 그 안에 그것을 static으로 선언한다. 다음으로 그 함수로 하여금 포함하고 있는 그 객체에 대한 레퍼런스를 리턴하도록 한다. 클라이언트는 이 객체를 참조하는 것보다는 함수를 호출할 것이다. 다른 말로 하자면 비지역 정적 객체를 함수 내의 static 객체로 대체 할 수 있다는 것이다. 이렇게 함수로부터 리턴된 레퍼런스는 초기화된 객체를 가르킨다고 보장받을 수있다. -> but!! 비지역 정적 객체를 흉내내는 함수를 호출하면서 그 객체를 생성하고 제거하는 비용을 들이게 된다.
48. 컴파일러의 경고(warning)에 주의를 기울여라.
함수를 재정의 할 경우 상위 클래스의 타입 혹은 const가 있는지 없는지에 따른 경고가 생길 수 있다. 이를 무시하는 것은 거의 분명하게 잘못된 프로그램 동작을 야기할 수 있고 따라서 컴파일러가 한 번에 감지한 잘못을 찾아내기 위해 수많은 디버깅이 수반될 것이다.
반면 구현에 따라 내재될 수밖에 없는 경고도 있다는 것을 기억해야한다.
49. 표준 라이브러리를 잘 알아두어라.
사용자의 클래스와 함수 이름으로 선택한 것과 같은 이름이 표준라이브러리에 이미 존재하고 있을 가능성이 있다 이런 충돌 가능성을 방지하기 위해 표준 라이브러리의 모든 것들은 std라는 이름의 네임스페이스에 포함되어 있다.
모든 스트림 클래스는 템플릿 클래스이고 사용자가 지정한 타입으로 스트림 클래스가 생성된다.
* 표준 C++의 라이브러리
- 표준 C 라이브러리
: 아직도 존재하며 사용할 수 있다.
- 입출력 스트림
: 기존의 입출력 스트림 구현과 비교해볼 때 템프릿화되었다는 것과 계승 계층도기 변경되었다는 것과 예외를 발생시킬 수 있다는 것과 문자열과 국제화를 지원한는 것이 달라졌다. 도한 string을 파일 스트림처럼 다룰 수 있으며 버퍼링과 포맷팅을 포함한 스트림 동작에 대해 광범위한 제어를 가질 수 있다는 것이다.
- 문자열
: string 객체는 모든 응용프로그램에서 char*를 사용할 필요성을 제거하기 위해 디자인되었다. 이것은 기대할 수 있는 모든 작업을 지원하며 기존의 코드와 호환성을 위해 char*로 변환 가능하고 메모리 관리를 자동으로 처리한다. 어떤 string 구현은 char*기반의 문자열보다 더 나은 성능을 보이는 레퍼런스 카운팅을 채택하기도 한다.
- 컨테이너
: 라이브러리에 벡터, 리스트, 큐, 스텍, 맵, 집합, 비트집합을 효율적으로 구현해놓은 것들이 존재한다. string도 역시 컨테이너이기대문에 컨테이너에게 할 수 있는 모든 작업을 string에게도 할 수 있다는 것이기 때문에 무척 중요하다.
- 알고리즘
: 표준 라이브러리는 스무가지 이상의 쉬운 방법(함수이며 공식적으로 알고리즘이라고 부른다. 사실 이것은 템플릿이다.)을 제공하는데 이들 대부분은 라이브러리에 있는 컨테이너를 갖고 하는 작업이다.
Ex) foreach, copy, search, equal, unique, rotate, sort…
- 국제화에 대한 지원
- 수 계산에 대한 지원
: 복수 클래스를 위한 템플릿뿐만 아니라 숫자 프로그래밍을 쉽게하기 위해 특별히 디자인된 특수한 배열 타입을 제공한다.
- 에러 진단에 대한 지원 :
'Sunmoon_BIT > C++ programing' 카테고리의 다른 글
RTTI (0) | 2010.04.15 |
---|---|
AVL 트리 회전 (0) | 2009.12.23 |
MyVector를 사용한 학생관리 - C++ (0) | 2009.11.20 |
Template - MyVector (0) | 2009.11.19 |
TEMPLATE - C++ (0) | 2009.11.17 |