[EC++] Chapter 1. C++에 왔으면 C++의 법을 따릅시다

huijae0817·2020년 8월 23일
1
post-thumbnail

요약

1. C++를 사용한 효과적인 프로그래밍 규칙은 경우에 따라 달라짐. 어떤 부분을 사용하느냐에 따라(크게 4가지 부분이 존재)

2. 단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각하자

3. 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각하자


[Item 1] C++를 언어들의 연합체로 바라보는 안목은 필수

  • 처음에는 '클래스를 쓰는 C'정도였으나 과감한 행보를 보임
  • 현재, 다중패러다임 프로그래밍 언어(multiparadigm programming language)라고 불림
    -절차적(procedural) 프로그래밍을 기본, 객체 지향(object-oriented), 함수식(functional), 일반화(generic) 프로그래밍을 포함하며, 메타프로그래밍(metaprogramming)개념까지 지원

적절하게 이해하는 방법?

  • 상관관계가 있는 여러 언어들의 연합체로 보아라.

  • 이 언어의 하위 언어(sublanguage)
    1. C: C를 기본으로 하고 있음
    2. 객체 지향 개념의 C++: 클래스를 쓰는 C. 클래스, 캡슐화, 상속, 다형성, 가상 함수(동적 바인딩)
    3. 템플릿 C++: 용도에 맞춘 템플릿 구문. 템플릿 메타프로그래밍(TMP)
    4. STL: 템플릿 라이브러리. 컨테이너(container), 반복자(iterator), 알고리즘(algorithm)과 함수객체(function object)가 얽혀 돌아가는 것이 규약

  • 이 네 가지의 하위언어로 이루어져있다는 것을 마음애 새기자

  • 한 하위언어에서 다른 하위언어로 옮겨갈 때, 각각의 규칙에 맞춰 적용
    -C는 값 전달이 참조 전달보다 효율 좋음
    -C++은 생성자, 소멸자 개념이 있고 참조 전달이 효율 좋음
    -템플릿 C++은 객체 타입조차 알 수 없음
    -STL은 C의 포인터를 본떠 만들었기 때문에 다시 값 전달이 효율 좋음


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

#define을 피하자

  • 가급적 선행 처리자보다 컴파일러를 더 가까이 하자!
#define Aspect_Ratio 1.653; // 매크로 표기(대문자로만)
  • 위와 같이 작성할 경우, 컴파일러는 Aspect_Ration를 기억하지 못해
  • 아래와 같이, 매크로 대신 상수를 쓰기
const double AspectRation = 1.653; // 상수 정의
  • 이렇게 사용되면 최종 코드의 길이도 짧아짐. 왜? 사용시 사본이 딱 한개만 생기기 때문.

  • #define을 상수로 교체할 시에, 주의해아 할 것.
    -상수 포인터 정의시, 포인터는 반드시 const선언, 가리키는 대상까지 const

    const char* const authorName = "Scot Meyers";
    const std::string authorName("Scot Meyers"); // 구닥다리 char* 말고 string	 

    -클래스 멤버로 상수 정의시, 사본 개수가 한 개를 넘지 못하게 하려면 static멤버로

   class GamePlayer{
   private:
    	static const int NumTurns = 5; // 상수 선언
       int scores[NumTurns];           // 상수를 사용
   }
  • 클래스 멤버가 선언된 시점에 초기값이 주어짐
    • 이것이 먹히지 않는경우(구식 컴파일러), 헤더 파일에서는 선언만 하고, 구현 파일에서 정의
    • 나열자 둔갑술(enum hank)기법 사용 추천.
    class GamePlayer{
    private:
       enum {NumTurns = 5}; // 나열자 둔갑술. 5에 대한 기호식 이름으로 만듦
       int scores[NumTurns];
    }
    • enum타입의 값은 int가 놓일 곳에 쓸 수 있기 때문
    • #define과 enum의 주소를 잡아내는것은 불법 / const는 합법 => enum은 좋은 lock이 될것
    • 별로인 컴파일러는 const에 대한 공간을 미리 잡아버림 / #define과 enum은 쓸모없는 메모리 잡지 않음
    • 템플릿 메타프로그래밍의 핵심 기법
      • 함수처럼 쓰이는 매크로를 만들때, const또는 inline함수를 우선 생각하자
      • 인라인 함수에 대한 템플릿 준비
      template<typename T>
      inline void callWithMax(const T& a, const T& b)
      {
      	f(a > b ? a : b);
      }
  • 주의해야할 점은, 클래스 상수를 #define으로 만들어서는 절대 안됨
    • 컴파일이 끝날 때까지 유효해짐
      • 클래스 상수 정의 불가하며 어떤 형태의 캡슐화 혜택도 받을 수 없음(private한 #define은 없음)
  • 그러니까, const, enum, inline의 도움으로 선행 처리자(특히 #define)의 사용을 줄일 수 있음
  • 아예 안쓸수는 없지만, 줄이도록 하자.

[Item 3] 낌새만 보이면 const를 들이대 보자!

  • const 는 팔방미인
    -의미적인 제약을 단단히 지켜줌(외부에서 변경 불가능)
    -namespace 유효범위의 상수를 선언하는데 사용 가능
    -파일, 함수, 블록 유효범위에서 static으로 선언한 객체에 사용 가능
    -정적 멤버 및 비정적 데이터 멤버 모두 상수 선언 가능

  • 포인터의 경우

    • 포인터가 가리키는 데이터를 상수 선언 가능
char greeting[] = "Hello";
char *p = greeting;               // 비상수 포인터, 비상수 데이터
const char *p = greeting;         // 비상수 포인터, 상수 데이터
char * const p = greeting;        // 상수 포인터, 비상수 데이터
const char * const p = greeting;  // 상수 포인터, 상수 데이터
- 함수 매개변수에서 포인터가 가리키는 대상을 상수로 만들 때 두 가지 방법을 취함
void f1(const Widget *pw);
 void f2(Widget const *pw);
  • STL 반복자의 경우

    • 반복자를 상수로 선언하려면
    std::vector<int> vec;
    
    const std::vector<int>::iterator iter = vec.begin();
    *iter = 10; (O)
    iter++;     (X)

    -반복자가 가리키는 대상을 상수로 선언하려면

    std::vector<int>::const_iterator cIter = vec.begin();
    *cIter = 10; (X)
    cIter++;     (O)
  • 가장 강력한 부분, 함수선언의 경우
    -함수 반환 값, 각각의 매개변수, 멤버 함수 앞에, 함수 전체에 const를 붙일 수 있다
    -함수 반환 값을 상수로 정하면 에러 돌발 상황을 줄이는 효과를 꽤 자주 볼 수 있음

    class Rational {...};
    const Rational operator* (const Rational& lhs, const Rational& rhs);
    
    Rational a, b, c;
    ...
    (a * b) = c; // 결과에 c를 저장? const라 어림도 없지~

    -위와 같이 ==를 써야 하는데 =를 쓴것과 같은 '쓸데없는'경우의 실수를 방지할 수 있음

    -상수 멤버 함수

    • 해당 멤버 함수가 상수 객체에 대해 호출될 함수이다 라는 사실을 알려 주는 것
    • 클래스의 인터페이스를 이해하기 좋게 하기 위해(변경할 수 있는 함수? 변경할 수 없는 함수?)
    • 상수 객체를 사용할 수 있게 하기 위해
      • 상수 객체에 대한 참조자로 진행 위해(실제 C++프로그램의 실행 성능을 높이는 핵심 기법 중 하나)
      • const 멤버 함수가 준비되어 있어야함
      class TextBook{
      
      public:
      	...
      	const char& operator[] (std::size_t position) const // 상수 객체에 대한 operator[]
      	{ return text[position]; }
      
      	char& operator[] (std::size_t position)             // 비상수 객체에 대한 operator[]
      	{ return text[position]; }
      
      private:
       std:: string text;

    };
    ~~~
    - 위의 경우, const키워드 유무에 따라 오버로딩되어있음 => 만들어진 객체에 따라 사용하는 operator가 달라짐
    ~~~
    TextBook tb("Hello"); // 비상수 객체
    std::cout << tb[0]; // 비상수 멤버 호출

     const TextBook cTb("World"); // 상수 객체
     std::cout << cTb[0];         // 상수 멤버 호출
    ~~~
     - 쓰임새의 구분
    	~~~
    	 tb[1] = 'x';  (O)
    	 cTb[1] = 'x';  (X) // const char& 타입에 대입 연산을 시도했기 때문
     	~~~
    	- 주의점: 비상수 오퍼레이터의 반환값이 char&이기 때문에 가능. char이라면, 컴파일 불가
    
    - 상수 멤버의 의미는?
    	 -비트수준 상수성(bitwise constness, 물리적 상수성): 어떤 멤버 함수가 그 객체의 어던 데이터 멤버도 건드리지 않아야 const임을 인정. 
      -논리적 상수성(logical constness): 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다는 것
      ~~~
      class CTextBook{
      public:
      	...
          char& operator[] (std::size_t position) const
          { return pText[position]; } 
      private:
      	char *pText;
      };
      
      // 이상한 코드(상수객체가 상수 멤버함수를 사용했는데 값이 변경됨)
      const CTextBlock cctb("Hello");
      char *pc = &cctb[0];
      *pc = "J"                       // cctb는 "Jello"를 가짐
      
      // 논리적 상수성에 따라 아래와 같은 멤버 추가 가능
      private:
      	char *pText;
          std::size_t textLength;
          bool lengthIsValid;
      };
      std::size_t CTextBook::length() const
      {
        if(!lengthIsValid){
         textLength = std::strlen(pText); // 상수멤버 내에서 private변수에 대입 불가..
          lengthIsValid = true;
          }
          return textLength;
      }
      ~~~
      - mutable의 사용
      	-비정적 데이터 멤버를 비트수준 상수성의 족쇄에서 풀어주는 키워드
          ~~~
          mutable std::size_t textLength; // mutable과 함께라면, 어떤 순간에도 수정 가능!
          mutable bool lengthIsValid;
          ~~~
    - 상수 멤버 및 비상수 멤버 함수에서 코드 중복 현상을 피하는 방법
    	- 일단 캐스팅은 피해라
     	-중복된 코드를 없애기 위해선 캐스팅이 불가피
      	- const_cast<char&>를 통해 반환값의 const를 떼어내고
          - static_cast<const TextBlock&>을 통해 const 객체를 가져옴
          - (*this) [position] 으로 *this타입의 캐스팅까지 하면 상수 버전의 op[]를 호출!
          => TextBlock에서 const TextBlock으로 바꾼것

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

0개의 댓글