블로그

항목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++

GStreamer 튜토리얼 - 1 : Hello World!

목표 모든 프로그래밍의 시작은 Hello World!를 출력하는 것에서 출발한다. GStreamer는 멀티미디어 프레임워크이므로 대신에 간단한 비디오를 출력해보는 것으로 시작한다. 하단에 매우 긴 소스코드가 있지만 실제 동작에 관여하는 코드는 4줄이며 나머지 부분은 메모리 관리와 같은 cleanup code 부분으로, 기본적으로 튜토리얼이 C로 쓰여져있기 때문에 사용되는 verbose한 부분이다. 소스 코드 다음은 웹상에 존재하는 비디오 리소스를 받아 실행하는 코드이다. #include <gst/gst.h> int main(int argc, char *argv[]) { GstElement *pipeline; GstBus *bus; GstMessage *msg; gst_init(&argc, &argv); pipeline = gst_parse_launch("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); gst_element_set_state(pipeline, GST_STATE_PLAYING); bus = gst_element_get_bus(pipeline); msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); if (msg != NULL) { gst_message_unref(msg); } gst_object_unref(bus); gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); return 0; } 소스코드를 빌드 후 실행하면 화면에 동영상이 실행된다. 다만, 비디오 리소스가 인터넷 상에 존재하므로 인터넷 속도에 따라 불러오는 데  시간이 조금 걸릴 수 있으며, 버퍼링과 같은 latency management는 이루어지지 않으므로 실행 도중에 멈출 수도 있다. 코드의 의미 /* Initialize GStreamer */ gst_init(&argc, &argv) 모든 GStreamer 코드는 gst_init()으로 시작하며 다음 역할을 수행한다. 모든 내부 구조 초기화 사용 가능한 플러그인 체크 Gstreamer에 의도된 모든 커맨드라인 옵션을 실행 (gst_init()에 인자로 &argc와 &argv를 전달하는 이유) pipeline = gst_parse_launch("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); 해당 코드는 이번 튜토리얼의 핵심이 되는 부분이며 두가지 부분을 주목해야 한다. gst_parse_launch GStreamer은 멀티미디어(동영상, 음성 등)의 흐름을 제어하는 프레임워크이며, 이 흐름은 Source(the producer)에서 Sink(the consumer)로 향하게 된다. 이 과정에서 중간에 여러 중간 요소를 거치게 되는데 이 요소들은 다양한 작업을 수행하게 된다. 이러한 요소들이 연결된 것을 Pipeline이라 부른다. GStreamer에서 이러한 pipeline을 수동으로 구성할 수도 있지만, 이번 튜토리얼과 같은 단순한 작업에 대해서는 복잡한 과정을 거칠 필요 없이 pipeline 구성을 자동으로 해주는 함수를 호출하여 해결 가능하다. 이러한 역할을 하는 함수가 바로 gst_parse_launch()이다. gst_parse_launch()는 텍스트로 이루어진 파이프라인 표현식을 실제 파이프라인으로 반환하는 간편한 함수이다. 위의 코드는 텍스트 형태로 playbin이라는 녀석과 파이프라인에 입력될 비디오를 URI라고 하는 주소의 형태로 전해주고 있다. playbin 그렇다면 우리는 이 playbin이라는 것이 무엇인지에 대해 알 필요가 있다. 앞서 설명하기를 멀티미디어의 흐름은 source에서 sink로 향하며 그 사이의 중간 과정들을 묶은 것을 pipeline이라고 하였다. 여기서 playbin은 그 자체로 source 및 sink의 역할을 하며 또한 완전한 파이프라인이다. 즉, 내부적으로 playbin은 멀티미디어를 실행할 수 있도록 해주는 요소를 생성하고 연결해주는 역할을 한다. 이번 튜토리얼은 playbin에 단 하나의 인자를 전달하고 있는데, 바로 우리가 실행하고자 하는 미디어의 URI이다. 해당 미디어가 https:// 프로토콜로 전달되는데, http:// 혹은 file:// 형식으로 전달되도 상관 없으며, 이에 따라 playbin은 적절한 GStreamer source를 인스턴스화하게 된다. playbin은 그 사용법이 간단하다는 장점이 있으나 원래대로라면 수동으로 pipeline을 구성해주는 과정을 자동화해주므로 우리는 세세한 과정에 대한 컨트롤이 불가하다는 단점이 있다. 그럼에도 다양한 방면에서 활용이 가능하도록 충분한 커스터마이징이 가능하므로 이번 튜토리얼과 같은 경우에 대해서는 충분히 사용 가능하다. /* Start Playing */ gst_element_set_state(pipeline, GST_STATE_PLAYING); 모든 GStreamer의 요소는 연관된 state를 가지고 있다. 예를 들어 DVD 플레이어의 Play/Pause가 대표적인 state의 예이며 이외에도 다양한 state가 존재한다. 현재로서는 pipeline을 PLAYING state로 세팅하지 않으면 재생을 시작하지 않는 경우로 충분하다. 따라서 해당 코드는 함수 gst_element_set_state()를 통해 pipeline을 PLAYING state로 세팅해 재생을 시작하는 의미이다. /* Wait until error or EOS */ bus = gst_element_get_bus(pipeline); msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); 해당 코드에는 Bus와 Message라는 것이 등장한다. 두 단어의 의미는 통상적인 의미와 비슷한데, message는 둘 이상의 객체간에 상호작용을 위한 메시지이고 bus는 이 메시지를 옮기는 데 사용되는 수단이다. 해당 pipeline에서 사용될 bus를 인스턴스화하기 위해 함수 gst_element_get_bus() 함수가 사용되었다. 또한 메시지를 만드는 데 사용한 함수 gst_bus_timed_pop_filtered()는  bus로 부터 message를 얻을 때 사용되며 받고자 하는 message type을 세번째 인자로 지정해 주었다 (Error 혹은 EOS, End-Of-Stream). 두번째 인자는 timeout을 지정해줄 수 있는데, GST_CLOCK_TIME_NONE은 timeout을 지정해주지 않고 영원히 기다리겠다는 의미이다. Cleanup 어플리케이션을 종료하기 전에 약간의 사후 처리 작업이 필요하다. /* Free Resources */ if (msg != NULL) { gst_message_unref(msg); } gst_object_unref(bus); gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); 인스턴스화한 pipeline과 bus 및 message는 작업이 끝나면 메모리 상에서 해제해주어야 한다. 이때 사용하는 함수가 gst_object_unref()와 gst_message_unref()이다. 다만 pipeline은 해제해주기 전에 함수 gst_element_set_state() 함수를 통해 NULL state로 만들어주는데 이는 pipeline을 통해 메모리에 할당된 어느 리소스라도 메모리 상에서 해제할 수 있도록 함이다. 최종적으로 pipeline까지 unreferencing을 함으로써 모든 객체가 메모리상에서 해제되고 프로그램이 종료된다. 요약 GStreamer Initialization : gst_init() Quick building pipeline from a textual description : gst_parse_launch() Creating an automatic playback pipeline : playbin Signaling Gstreamer to start playback : gst_element_set_state() Leaving the left process up to GStreamer : gst_element_get_bus() & gst_bus_timed_pop_filtered() 출처 Gstreamer Basic Tutorial 1: Hello World!

GStreamerCC++

항목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++

채널톡 아이콘