1장 C++에 왔으면 C++의 법에 따릅시다.

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

 C++은 다중 패러다임 프로그래밍 언어이다.
 +---- 절차적(procedural) 프로그래밍
 |
 +---- 객체지향(object-oriented)
 |
 +---- 함수식(functional)
 |
 +---- 일반화(generic)프로그래밍
 |
 +---- 메타프로그래밍(metaprogramming)

 C++의 하위언어
 +---- C :
 |
 +---- 객체 지향 개념의 C++ : 클래스, 캡슐화, 상속, 다형성, 가상함수(동적 바인딩)
 |
 +---- 템플릿 C++
 |
 +---- STL : 컨테이너(container), 반복자(iterator), 알고리즘(algorithm), 함수 객체(function object)
 
#2. #define을 쓰려거든 const, enum, inline을 떠올리자
 선행처리자(pre-complier)보다 컴파일러를 더 가까이하자.
 
 (1) 매크로 대신 상수를 쓰자.
 #define ASPECT_RATIO 1.653
 --> const double AspectRation = 1.653;
 
 코드에서 숫자상수로 바뀌기 때문에, 기호 테이블(symbolic table)에 등록되지 않는다.
 
 const char* const authorName = "Sccot Meyers";
 const std::string authorName("Sccot Meyers");
 
 (2) 클래스 맴버로 상수를 정의하는 경우
 class CSomething{
  static const int NumTurns = 5;
 };
 static 멤버로 만든다. -> 유효영역이 클래스영역으로 제한되어 캡슐화에 용이하다.

 선언 방법
 a. 정적 맴버 변수가 정수형일 경우에는 선언과 동시에 초기화 가능
 b. 그외의 타입은 구현부분에서 초기화

 class CSomething {
  static const double FudgeFactor;
 };
 const double CSomething::FudgeFactor = 1.35;


 (3) enum 나열자 둔갑술
 나열자(enumerator)타입은 int가 놓일 곳에도 쓸 수 있다.
 
 예) 배열의 크기를 정의할때
 class CSomething {
 private:
  enum { NumTurns = 5 };
 
  int scores[NumTurns];
 };
 
 #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
 
 int a = 5, b = 0;
 CALL_WITH_MAX( ++a, b );
 
 template <typename T>
 inline void callWithMax(const T& a, const T& b)
 {
  f(a > b ? a : b);
 }

#3. 낌새만 보이면 const를 들이대 보자!
 char greeting[] = "Hello";
 
 const char *p = greeting; // 상수 데이터, 비상수 포인터
 char * const p = greeting; // 비상수 데이터, 상수 포인터
 
 const <- * : 데이터
 * -> const : 포인터
 
 void f1(const Widget *pw); == void f1(Widget const *pw);
 // 둘다 같은 용법이다. * 왼쪽에 const는 마음데로 위치할 수 있다.
 
 std::vector<int> vec;
 
 const std::vector<int>::iterator iter = vec.begin();
 *iter = 10;  // 데이터 수정 가능
 ++iter;   // iterator 변경 불가능

 std::vector<int>::const_iterator cIter = vec.begin();
 *cIter = 10; // 데이터 수정 불가능
 ++iter;   // iterator 변경 가능
 
#4. 객체를 사용하기 전에 반드시 그 객체를 초기화 하자.
 직접 초기화, 생성자 초기화
 C++ 규칙 어떤 객체이든 그 객체의 데이터 맴버는 생성자의 본문이 "실행되기 전"에 초기화 되어야 한다.
 
 초기화 순서
 1. 기본 클래스는 파생클래스 보다 먼저 초기화된다.
 2. 클래스 데이터 맴버는 선언된 순서대로 초기화된다.
 3. 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다.
    (--> 초기화 시점이 달라질수 있으므로 싱글턴 패턴을 이용하여 해결하자)
   
   
2장 생성자, 소멸자 및 대입연산자

#5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자.
 클래스에 선언하지 않으면 컴파일러가 저절로 선언해 주는 것.
 1. 복사 생성자
 2. 복사 대입 연산자
 3. 소멸자
 
 class Empty{
 public:
  Empty() {}        // 기본생성자
  Empty(const Empty& rhs) {}    // 복사생성자
  ~Empty() {}        // 소멸자
 
  Empty& operator=(const Empty& rhs){} // 복사 대입 연산자
 };
 
 Empty e1;         // 기본생성자, 소멸자
 
 Empty e2(e1);        // 복사 생성자
 
 e2 = e1;         // 복사 대입 연산자
 
 암시적인 복사 대입연산자를 사용하지 못하도록 하는 방법
 기본 클래스에서 복사 대입 연산자를 private로 선언하면,
 파생 클래스에서 암시적 복사 대입 연산자를 가질수 없다.
 
#6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자.
 HomeForSale h1;
 HomeForSale h2;
 
 HomeForSale h3(h1);  // 복사생성자 컴파일 금지
 h1 = h2;    // 복사 대입 연산자 컴파일 금지
 
 해결방법 : 컴파일러가 생성하는 함수는 모두 공개된다.
 private로 복사생성자와 복사대입연산자를 선언하면 컴파일러는 자동 생성을 할 수 없다.
 명시적 선언을 하면 컴파일러는 자동생성을 하지 않고, private로 선언했으므로 외부에서 접근 불가.
 
 friend 접근까지 막으려면, 함수를 선언만 하고 정의하지 않으면 된다.
 class HomeForSale {
  HomeForSale(const HomeForSale&); // 선언만 딸랑하고 인자값도 넣지 않는다.
  HomeForSale& operator = (const HomeForSale&);
 };
 위와 같이 하면 링크시점에서 에러 발생
 
 컴파일 시점 에러발생을 하려면, 기본 클래스를 상속받으면 된다.
 class Uncopyable {
 protected:
  Uncopyable() {}
  ~Uncopyable() {}
 
 private:
  Uncopyable(const Uncopyable&);
  Uncopyable& operator = (const Uncopyable);
 };
 이 클래스를 상속 받으면 된다.(public 상속이 아니어도 된다.)
 class HomeForSale : private Uncopyable {
 };
 
#7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자.
 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해야 한다.
 기본클래스가 아니거나 다형성이 필요없는 클래스에서 가상 소멸자를 선언하지 말자!
 
#8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자.
 예외 발생가능성이 있는 코드는 보통함수에서 깔끔하게 처리한다.
 소멸자에서 예외 발생 가능성이 포함된 코드실행 및 함수호출은 하지 않는다.
 
 소멸자에게 예외처리를 맡기지 말자.
 
#9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자.
 
 이부분은 넘어가자
 
#10. 대입 연산자는 *this의 참조자를 반환하게 하자.
 관례적인 성향이 있는 규칙( =, +=, -=, *=, /=, <<, >> )
 class Widget {
 public:
  Widget& operator=(const Widget& rhs)  // 반환타입은 클래스의 참조자
  {
   return *this;       // 좌변 객체의 참조자를 반환합니다.
  }
 };
 
#11. operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자.
 // 사본을 만들어 바꿔치기 한다.
 Widget& Widget::operator=(const Widget &rhs)
 {
  Widget temp(rhs);
 
  swap(temp);
 
  return *this;
 }
 
 // 명확성은 떨어지나 기술적인 코드
 Widget& Widget::operator=(Widget rhs) // 값에 의한 전달로 사본 전달
 {
  swap(rhs);
 
  return *this;
 }
 
#12. 객체의 모든 부분을 빠짐없이 복사하자.
 복사 생성자, 복사 대입 연산자.
 class PriorityCustomer : public Customer {
 };
 
 // 복사생성자 처리
 PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
 : Customer(rhs),      // 기본클래스의 복사 생성자를 호출합니다.
   priority(rhs.priority)
 {
 }
 
 // 복사 대입 연산자 처리
 PriorityCustomer& PriorityCustomer::operator =(const priorityCustomer& rhs)
 {
  Customer::operator=(rhs);   // 기본 클래스 부분을 대입합니다.
 }


3. 자원관리

#13. 자원관리에는 객체가 그만!
 std::auto_ptr<Investment> pInv(createInvestment());
 (1) 자원을 획득한 후에 자원 관리 객체에게 넘긴다.
 (2) 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 합니다.
 
 참조 카운팅 방식 스마트 포인터(refrence-counting smart porinter: RCSP)
 std::tr1::shared_ptr<Investment> pInv(createInvestment());
 
#14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자.
 자원 획득 즉 초기화(RAII : Resource Acquisition Is Initialization)기법
 RAII 객체가 복사될 때 어떤 동작이 이루어져야 할까요?
 1. 복사를 금지합니다.
  class Uncopyable을 상속 받는다.
 2. 관리하고 있는 자원에 대해 참조 카운팅을 수행합니다.
  tr1::shared_ptr 이용
 3. 관리하고 있는 자원을 진짜로 복사합니다.
  깊은복사(Deep copy)로 모두 복사
 4. 관리하고 있는 자원의 소유권을 옮깁니다.
  auto_ptr의 복사 동작(swap)
 
#15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자.
 자원관리 클래스의 자원을 참조하여 사용할 수 있어야 한다.
 자원 변환 방법
 (1) 명시적 변환
 (2) 암시적 변환
 맞게 쓰기는 쉽게, 틀리게 쓰기는 어렵게 하자.
 std::string str;
 str.c_str();  // 명시적인 변환을 사용하면 암시적인 변환보다 편하진 않지만, 꼭 필요한 곳에서 맞게 쓸 수 있다.

#16. new 및 delete를 사용할때는 형태를 반드시 맞추자.
 CSomething pSomething = new CSomething;
 delete pSomething;
 
 std::string *stringArray = new std::string[100];
 delete [] stringArray;
 
#17. new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자.
 스킵

2009/07/22 21:51 2009/07/22 21:51

글 걸기 주소 : 이 글에는 트랙백을 보낼 수 없습니다

덧글을 달아 주세요

  1. TTF 2009/07/22 21:52 고유주소 고치기 답하기

    온라인 스터디를 위한 요약 글입니다.