블로그

항목2 : #define을 쓰려거든 const, enum, inline을 떠올리자.

#define을 쓰려거든 const, enum, inline을 떠올리자.   #define 정의명은 오류메세지나 코드에서 구별하기 어렵다. #define은 클래스 상수를 정의할 수 없고 어떤 형태의 캡슐화 혜택도 못받는다.   1. #define대신 const 사용.     2. const 사용 주의사항 - 상수 포인터를 정의하는 경우엔 포인터가 가르키는 대상까지 const화 해주어야한다.   ex1) const int value = 5; const int* ptr = &value;   ex2) const char* const authorName = "Scott Meyers";             사용할 수 있다면 char* 보단 string을 사용하자.              const std::string authorName("Scott Meyers");   - 클래스 상수 정의하는 경우 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶다면 정적(static) 멤버로 만들자.       3. 배열 멤버를 선언할 때 enum을 사용하자. enum은 컴파일러 입장에서 참고하는 값이기 때문에 상수 처리된다.  enum은 메모리를 잡아 먹지 않는 장점이 있다.   4. 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수 우선 생각하자. #define CALL_WITH_MAX(a, b) f( (a) > (b) ? (a) : (b) ) 위 와 같은 예시는 단점이 많다. 매크로의 효율을 유지하고  타입 안정화까지 취할 수 있는 인라인함수에 대한 템플릿 사용. -> template<typename T>      inline void callWithMax( const T& a, const T& b)       { f ( a > b ? a  :  b )      } T가 정확히 뭔지 모르기 때문에 매개변수로 상수 객체의 주소 참조한다.           책을 읽으면서 찾아본 내용   오버헤드(overhead) 어떤 처리를 하기 위해 들어가는 간접적인 시간, 메모리 할당량 ( A의 실행은 단순 10초지만, 안정성 고려해 부가적 B라는 처리를 추가하여 처리시간이 15초 이때 오버헤드는 5초)   인라인 함수(inline function) 내부에서 작성된 코드의 속도와 함수의 장점을 결합하는 방법이다. 컴파일러에서 함수를 인라인 함수로 처리하도록 요청. 컴파일러가 코드를 컴파일하면 모든 인라인 함수가 in-place 확장된다. 즉, 함수 호출이 함수 자체의 내용 복사본으로 대체되어 함수 오버헤드가 제거된다. 단점은 인라인 함수를 자주 호출하거나 함수가 길면 코드를 더 크게 만들게 된다. 인라인화 할 함수는 내부 루프가 없는 짧은 함수에 가장 적합하다.   -> 인라인 함수를  알고 있어야 하지만 최신 컴파일러는 함수를 적절하게 인라인화 하므로 inline 키워들 사용 안해도 된다.        

CPPC++EffectiveC++

항목11 - operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자!

operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자!   자기대입이란(self assignment)? 어떤 객체가 자기 자신에 대해 대입연산자를 적용하는 것을 말함 w = w; a[i] = a[j]; *px = *py; 같은 값을 같거나 같은 값을 가르키면 그것은 자기대입이 성립됨. 자기대입이 생기는 이유는 여러 곳에서 하나의 객체를 참조하는 상태인 '중복참조(aliasing)' 라고 불리는 것 때문!   자기대입이 안좋은 이유 - 코드가 커짐 - 처리 흐름에 분기점을 만들어 실행속도 느려짐 - CPU 명령어 선행인출, 캐시, 파이프러닝 등의 효과 떨어짐   해결책  - ' 옮긴 후 삭제! '  1. 원래의 포인터변수 pb를 어딘가에 저장한다. Bitmap *pOrig = pb; 2. pb 가 *pb의 사본을 가리키게 한다. pb = new Bitmap(*rhs.pb); 3. 원래의 pb를 삭제한다. delete pOrig; - > new Bitmap 부분에서 예외가 발생하더라도 pb는 변경되지 않은 상태가 유지되어 예외에서 안전하다.   - '복사 후 맞바꾸기(copy and swap)' 1.  temp = 사본의 copy를 하나 만들고 그것을 원본과 스왑하는 기법 -> 클래스의 복사 대입 연산자는 인자를 값으로 취하도록선언하는 것이 가능하다는 점 -> 값에 의한 전달을 수행하면 전달된 대상의 사본이 생긴다는 점   잊지말자! - operator= 을 구현할 때, 어떤 객체가 그 자신에 대입되는 경우를 제대로 처리하도록 만듭시다. 원본 객체와 복사대상 객체의 주소를 비교해도 되고, 문장의 순서를 적절히 조정할 수도 있으며, 복사 후 맞바꾸기 기법을 써도 된다! - 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인해 보자!  

CPPC++EffectiveC++

LV1 - 최소직사각형 만들기

<algorithm>의 min, max 함수에 대한 이해랑 숙련도가 부족했다.  min, max를 생각을 미처 못했다. - min(a, b) : a와 b를 비교하여 가장 작은 값을 반환한다. - max(a, b) : a 와 b를 비교하여 가장 큰 값을 반환한다.   내 풀이 int solution(vector<vector<int>> sizes) {   int answer = 0;   vector<int> W;   vector<int> H;   vector<int> temp;   int maxSize;   bool moreBig = false;   // 가로 세로 vector로 분리.   for (int i = 0; i < sizes.size(); i++)   {           W.push_back(sizes[i][0]);           H.push_back(sizes[i][1]);   }   // 가로 세로 오름차순으로 정렬.   sort(W.begin(), W.end());    sort(H.begin(), H.end());   // 가로 세로에서 가장 큰 수 찾고 가로에 큰 수 있으면 moreBig true 세로에 큰 수가 있으면 false.   maxSize = W.back() > H.back() ? W.back() : H.back();    moreBig = W.back() > H.back() ? true : false;   if (moreBig == true)   {       for (int i = 0; i < sizes.size(); i++)       {           if (sizes[i][0] < sizes[i][1])               sizes[i][1] = sizes[i][0];                 temp.push_back(sizes[i][1]);       }       sort(temp.begin(), temp.end());       answer = maxSize * temp.back();   }   else   {       for (int i = 0; i < sizes.size(); i++)       {           if (sizes[i][0] > sizes[i][1])               sizes[i][0] = sizes[i][1];           temp.push_back(sizes[i][0]);       }       sort(temp.begin(), temp.end());       answer = maxSize * temp.back();   }   return answer;} 잘한 사람 풀이 int solution(vector<vector<int>> sizes) {    int answer = 0;   int w = 0, h = 0;   for (int i = 0; i < sizes.size(); i++)   {       w = max(w, min(sizes[i][0], sizes[i][1]));       h = max(h, max(sizes[i][0], sizes[i][1]));   }   answer = w * h;   return answer;}

CPPC++알고리즘

항목8 - 예외가 소멸자를 떠나지 않도록 붙들어 놓자

예외가 소멸자를 떠나지 못하도록 붙들어 놓자.   소멸자가 호출 되는 경우 1. 정상적으로 객체가 종료되었을 때 2. 예외처리 메커니즘에 의해 객체가 소멸될 때   소멸자가 예외가 있으면 안되는 이유 예를 들어 10의 크기를 갖는 벡터 v가 있다. 함수를 사용한 다음에서야 10개 만큼 메모리가 해제된다. 만약 v의 첫번째 주소에서 문제가 생겨 예외가 발생했다면??? -> 나머지 2~10의 메모리가 누수되는 것이다.   다른 예를 들어   class DBConn { public: ~DBConn() { db.close(); }   private: DBConnection db; };   위 코드에서 프로그램이 종료되기 위해 ~DBConn() 호출하였는데, 여기서 예외 발생 시,  프로그램이 미정의 동작을 발생시킬 것이다. 그냥 예외가 남은채로 끝나는 것이다.   소멸자에서 예외가 나면 해결방법! 1. close에서 예외가 발생하면 프로그램을 바로 끝내라. -> 에러 발생 후에 프로그램 지속이 어려운 경우 괜찮은 선택이다. 2. close를 호출한 곳에서 일어난 예외를 무시하라. -> 예외를 무시한 뒤라도 프로그램이 신뢰성 있게 실행 될 수 있는 상태일 경우. 3. close 호출 책임을 소멸자에서 사용자로 넘겨라!      정리 1. 일반적으로 C++은 예외를 내보내는 소멸자를 좋아하지 않는다. 2. 예외는 소멸자가 아닌 다른 함수에서 비롯된 것이어야 한다! -> 소멸자에 있다면 사용자는 예외에 대처할 기회가 없다. 임시방편으로 무시할뿐. 3. 소멸자에서 예외가 발생하지 않도록 하자! -> 예외는 소멸자가 아닌 함수에서 처리하도록 하자! 4. 소멸자에서 호출되는 함수가 예외 가능성이 있다면 -> 소멸자에서 삼켜버리던지, 프로그램을 종료하던지 처리하자. 5. 소멸자에서 예외가 생길 경우 자체를 배제해서 코드를 작성하는 것이 최선이다.

CPPC++EffectiveC++

항목5 - C++가 은근슬쩍 만들어 호출해 버리는 함수에 촉각을 세우자!

항목5 : C++가 은근슬쩍 만들어 호출해 버리는 함수에 촉각을 세우자!   1. 컴파일러가 자동으로 선언해주는 함수 - 기본 생성자 - 복사 생성자(copy constructor) - 복사 대입 생성자(copy assignment opertator) - 소멸자(destructor) 2. 복사 생성자가 하는일은 원본 객체의 비정적 데이터를 사본 객체 쪽에 옮기는 일. 3. 복사 대입 연산자 같은 경우 '적법' 과 '합리적'을 따져 조건에 부합하지 않으면 컴파일러는 기본적으로 생성하지 않는다. 4. 멤버변수가 참조자, 상수로 되어 있다면 '복사 대입 연산자' 가 자동으로 생성되지 않고 컴파일 에러 발생.     ->  C++의 참조자는 원래 자신이 참조하고 있는 것과 다른 객체를 참조할 수 없기 때문이다.( 에러 발생) 5. private 영역에 대해 '복사 대입 연산자' 를 생성한 경우 접근이 불가능하다. 따라서 해당 경우엔 컴파일러가 암시적 생성 거부를 선언한다(상속클래스라면 private을 protected로 바꾸면됨)  6. const의 경우에도 상수성인 값을 복사하는 행위자체가 적법하지 않다고 판단해 5번같이  컴파일러가 암시적 생성 거부 선언.   생성자 공부 메인함수에서 객체 선언시,  그냥 선언하면 '기본 생성자' 호출 ex) Test t1; 객체 생성하면서 인자를 넣어주면 '복사 생성자' 호출 ex) Test t2(t1); 객체 생성하면서 같은 타입을 대입 연산한다면 '복사 생성자' 호출 ex) Test t3 = t1; 일반 상황에서 대입 연산을 한다면 '복사 대입 연산자'를 호출 ex) t1 = t2; Call-by-Value 시, 복사 생성자 사용하여 새로운 객체 생성.     기억하자! 컴파일러는 모든 경우가 아닌 경우에 따라 "생성자", "소멸자", "복사 생성자", "복사 대입 연산자"를 암시적으로 생성하는 걸 기억하자.  

CPPC++EffectiveC++

항목4 - 객체를 사용하기 전에 반드시 그 객체를 초기화하자.

객체를 사용하기 전에 반드시 그 객체를 초기화하자.   1. 모든 객체를 사용하기 전에 항상 초기화를 하는 게 좋다.( int, pointer 등) 2. 대입과 초기화를 헷갈리지 말자. 3. 생성자를 좀더 세련되게 쓰기 위해선 대입문 대신에 초기화리스트를 사용하자. 4. 상수이거나 참조자는 대입이 불가능하기때문에 초기화리스트로 반드시 초기화해주자. 5. 생성자마다 초기화리스트 달린 것이 보기좋지않다면 함수로 빼내어 모든 생성자가 해당 함수를 호출하게 해주자. 6. 객체를 구성하는 데이터의 초기화 순서 - 기본 클래스는 파생클래스보다 먼저 초기화 - 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화   (멤버초기화리스트에 넣어진 순서가 달라도 순서는 그대로. 그래도 순서대로 넣어주자.)   7. 정적 객체(static object) : 프로그램이 끝날때까지 살아 있는 객체   정적 객체의 범위 - 우선 전역 객체가 있고 - 네임스페이스 유효범위에서 정의된 객체 - 클래스 안에 static으로 정의된 객체 - 함수 안에서 static으로 선언된 객체 - 파일 유효범위 안에서 static으로 정의된 객체 - 함수 안에 있는 정적 객체는 지역 정적 객체(local static object) - 이외 정적 객체는 비지역 정적 객체(non-local static object)   8. 별개의 번역단위에서 정의된 비지역 객체들의 초기화 순서는 '정해져 있지 않다'.   -> ?) 초기화 자체가 보장이 안되는 것인가, 초기화 순서가 보장이 안되는 것인가?   해결방법 - 비지역 정적객체 전용함수를 만들고 함수내에 해당객체를 지역 정적객체로 선언하여 해당 레퍼런스를 반환.     잊지말자! 기본타입객체는 직접 초기화해라. 초기화 리스트를 사용해라. 여러 번역단위에 있는 비지역 정적객체들의 호출 순서문제를 피하여 설계해라.

CPPC++EffectiveC++

항목3 : 낌새만 보이면 const를 들이대 보자!

1. const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 준다. 2. const는 어떤 유효범위에 있는 객체에도 붙을 수 있으며, 함수 매개변수 및 반환타입에도 붙을 수 있고, 멤버 함수에도 붙을 수 있다. 3. 컴파일러 쪽에서 보면 비트수준 상수성을 지켜야 하지만, 개념적인(논리적인) 상수성을 사용해서 프로그래밍해야 한다. 4. 상수멤버와 비상수멤버가 기능적으로 똑같이 구현되어 있다면 코드 중복을 피하는 것이 좋은데,     이때 static_cast를 사용해 비상수 버전이 상수 버전을 호출 할 수 있게 한다.( 캐스팅이 필요하지만 안전성도 유지하면서 코드 중복을 피할 수 있는 방법 )    상수 객체를 통해 비상수 함수를 호출하는 것은 옳지 않은 방법이다.(컴파일러 에러 발생)   의미적으로 상수를 표현하기보단 const를 통해 상수를 표현하는 것이 안정적인 방법이다.   상수 멤버 함수를 사용하는 이유 1. 클래스의 인터페이스를 이해하기 좋게 하기 위함 2. const 키워드를 통해 상수 객체를 사용할 수 있게 하자는 것    C++ 실행 성능을 높이는 핵심 기법 중 하나가 '객체전달'을 '상수객체에 대한 참조자'로 진행하는 것이다.    이 기법을 제대로 활용하려면 상수 상태로 전달된 객체를 조작할 수 있는 const 멤버 함수, 즉 상수 멤버 함수가 준비되어       있어야한다.  

CPPC++EffectiveC++