'연산자 함수'는 연산자를 이용하듯 호출할 수 있는 메서드입니다. 아래 코드를 살펴보겠습니다. void main() { int a = 10, b = 20; a + b; // '10 + 20'으로 30이 됩니다. MyData c, d; c + d; // '사용자 정의 타입'끼리의 덧셈? } 수학적으로 봤을 때 '+' 연산자는 각각 좌, 우측의 값이 'int'형 정수인, 정수 값끼리의 덧셈입니다. 하지만, 가만히 생각해보면 '+' 연산은 굳이 숫자에만 적용할 수 있는 것이 아닙니다. 예를 들어, String 타입간의 '+' 연산은 알다시피 두 문자열이 합쳐집니다. void main() { string a{ "Hello, " }, b{ "world" }; string c = a + b; // 'c'는 당연히 ..
'이동 생성자' 개념을 다루기 전에 'r-value' 개념을 먼저 잡아야합니다. 간단히 'r-value'는 단순 대입 연산자의 오른쪽 항을 말합니다. 코드를 보겠습니다. void main() { int a = 3 + 4; // '3 + 4'를 계산해 'a' 변수에 대입합니다. int&& b = 3 + 4; // '3 + 4' 자체를 'b' 변수가 참조합니다. b++; } 'r-value'란 개념은 이전부터 있던 개념입니다. 하지만 'r-value를 참조한다'라는 개념은 C++11에 와서 생겨났습니다. 왜 'r-value 참조'가 필요한지 다음 코드를 보겠습니다. void Func(int& param) { /* do somthing */ } void main() { int a = 10; Func(a); /..
파일 입출력 유닉스 시스템에서는 거의 모든 것을 파일로 표현하므로 '파일 입출력'은 매우 중요한 부분입니다. 알다시피 파일은 '읽기'나 '쓰기' 전에 반드시 '열기(open)'를 해야합니다. 그리고 커널은 '파일 테이블'이라고 하는 프로세스별로 열린 파일 목록을 관리합니다. 각 프로세스에는 기본적으로 0, 1, 2 값을 가지는 파일 디스크립터가 open되어 있습니다. 0 - 표준 입력(stdin) 1 - 표준 출력(stdout) 2 - 표준 에러(stderr) 위의 3가지 파일 디스크립터를 직접 참조하는대신 C 라이브러리는 #define 매크로 정의를 제공합니다. 따라서, 해당 파일 디스크립터를 사용하고싶다면 아래 정의된 매크로로 참조하는게 좋습니다. #define STDIN_FILENO 0 #defin..
간단하게 매개변수가 한 개인 생성자를 '변환 생성자'라고 합니다. 이러한 '변환 생성자'는 '사용자 코드'에서의 편의를 위해 묵시적으로 호출될 수 있습니다. 예제를 살펴보겠습니다. class MyData { private: int m_nData = 0; public: MyData(int nParam) : m_nData{ nParam } { cout
'복사 생성자'는 다른 인스턴스의 값을 그대로 복사해서 새로운 인스턴스를 생성할 때 사용됩니다. '복사 생성자'가 호출되는 경우는 2가지가 있습니다. 1. 명시적으로 객체의 복사본을 생성하는 방식으로 선언 2. 함수 형태로 호출되는 경우 - 클래스가 매개변수로 사용되는 경우 - 클래스가 반환 형식으로 사용되는 경우 1. 명시적으로 객체의 복사본을 생성하는 방식으로 선언 먼저 첫 번째 경우를 코드로 살펴보겠습니다. void main() { int a = 10; int b = a; } 위의 예제에서 'a'의 값을 'b'에 대입(복사)하면서 생성하였습니다. 이 원리를 클래스에 적용시킨 것이 '복사 생성자'입니다. class MyData { private: int m_nData = 0; public: MyDat..
C언어에서는 프로그램 내에서 공유하는 변수나 함수를 만들기 위해 '전역'으로 선언 및 정의합니다. 하지만, C++에서 '전역'은 그다지 좋은 선택이 아닙니다. 객체지향 프로그래밍에서 어떤 클래스의 소속 없이 독립적으로 존재하는 '전역변수'나 '전역함수'는 설계상으로도 좋지않고, 불필요한 의존관계를 만들 수도 있습니다. 어떤 클래스의 소속에 있으면서 '전역'처럼 사용할 수 있도록 만들어진것이 바로 '정적(static) 멤버'입니다. 쉽게 말해, '전역변수'나 '전역함수'가 클래스 내에 들어옴으로써 최소한의 '소속'을 가지도록 했다는게 핵심입니다. 다음 예제는 일반적인 사용 예입니다. class MyData { private: int m_nData; static int m_nCount; // 정적 멤버변수 ..
알다시피 'const' 키워드는 '읽기'만 하는 곳엔 무조건 붙여주는게 좋습니다. 만약 '읽기' 기능만 있는 멤버함수라면 당연히 'const' 키워드를 붙여줌으로써 멤버함수를 상수화할 수 있습니다. 하지만, 미래에 코드를 수정하면서 상수화시켰던 멤버함수에 '쓰기' 기능을 추가해야한다면 어떻게 해야 할까요? 이런 상황에 맞닥뜨릴때 사용할 수 있는 것이 'mutable' 예약어와 'const_cast' 형변환 연산자입니다. 상수화된 멤버함수는 '읽기'만 가능하지만, 'mutable' 키워드로 선언된 멤버변수에는 '쓰기'가 가능합니다. class MyData { private: mutable int m_nData = 0; public: MyData(int nParam) : m_nData{ nParam } { ..
C99에서 생겨난 'inline 함수'는 쉽게 말해 함수 호출에 의한 오버헤드가 없는 함수입니다. 간단한 예제를 살펴보겠습니다. int Add(int a, int b) { return a + b; } void main() { int val = Add(10, 20); } 위의 예제에서 Add()는정말 간단한 기능을 하는 함수입니다. 다들 알고있다시피, 함수를 호출하기 위해선 비용이 발생합니다. 여기서 말하는 비용이란 함수에 대한 스택 프레임을 새로 만들고 바로 전에 있는 스택 프레임에 대한 데이터를 레지스터에 백업해야하는 등의 일입니다. 함수가 제공하는 기능에 비해 함수를 호출하는데 드는 비용이 상대적으로 더 큰, 배보다 배꼽이 더 큰 경우가 되어버립니다. 초창기 이것의 대안으로 생각해낸게 '매크로 함수'..
'디폴트 매개변수'란 말그대로 매개변수의 디폴트 값을 지정할 수 있다는 것을 의미합니다. 간단한 시나리오로 예를 들어보겠습니다. // 2013년에 A개발자가 만든 함수(라이브러리) int CalcLayout(int nWidth, int nHeight) { return nWidth + nHeight; } // 2013년에 B개발자가 해당 함수(라이브러리)를 사용합니다. void main() { CalcLayout(25, 50); } 시간이 흐른 후, A개발자는 이전에 만들었던 함수(라이브러리)를 수정하려고 합니다. // 2013년에 A개발자가 만든 함수(라이브러리) -> A개발자는 2019년에 해당 함수(라이브러리)를 수정하려합니다. int CalcLayout(int nWidth, int nHeight, ..
'템플릿(template)'은 타입을 추상화하는 방법입니다. 타입을 추상화한다는게 무슨 말일까요? 아래 예제는 정수 값을 담을 'Array 컨테이너'입니다. class Array { private: int* buf; int size; // 원소의 개수 int capacity; // 저장 가능한 메모리 크기 public: explicit Array(int cap = 256) : buf{ nullptr }, size{ 0 }, capacity{ cap } { buf = new int[capacity]; } ~Array() { delete[] buf; } void Add(int data) { if (size < capacity) buf[size++] = data; } int operator[](int inde..
'함수 객체(Functor)'를 유용성을 알아보기 위해서는 먼저 '함수 포인터를 이용한 콜백 메커니즘'을 알아봐야합니다. 여기서 '콜백 메커니즘'이 뭘까요? '콜백 메커니즘'의 개념을 설명하기 위해선 '서버 코드'와 '클라이언트 코드'의 개념이 필요합니다. 서버 코드: 기능이나 서비스를 제공하는 코드 클라이언트 코드: '서버 코드'가 제공해주는 기능이나 서비스를 사용하는 코드 간단한 예제를 살펴보겠습니다.d // 서버 코드 void Print() { cout
C++의 '자원 핸들(자원 관리 메커니즘)'의 핵심은 생성자와 소멸자입니다. 간단한 '스마트포인터 컨테이너'를 만들어보면 쉽게 이해할 수 있습니다. class Point { private: int x; int y; public: Point(int _x = 0, int _y = 0) : x{_x}, y{_y} {} void Print() const { cout