목록프로그래밍 (42)
INDIES
포인터는 C/C++을 강력한 언어로 만들어주는 도구이면서 동시에 굉장히 다루기 까다로운 언어로 만들어주는 도구이다. 왜 포인터는 어렵고 짜증나는 개념인가? 그 이유를 대충 요약하자면 아래 6가지 정도의 항목이 나온다.포인터가 가리키는 주소의 값이 배열인지 하나의 객체인지 판별이 불가능하다.포인터가 가리키고 있는 주소의 값을 내가 다 쓰고 나서 파괴해야할지 아닐지 판단하는게 불가능하다. 즉, 포인터가 가리키는 대상을 내가 소유하고 있는지 아닌지 알 수 있는 방법이 없다.만약에 포인터가 가리키는 대상을 파괴하기로 마음먹었다고 하자. 문제는, 그걸 파괴하기 위해 어떤 방법을 써야할 지 알아낼 방법이 없다는 것이다. 모든 자원을 다 delete를 써서 제거하는 것은 아니다. release 등의 특정 함수를 호출..
special member functionC++에서 special member function이란 따로 명시하지 않아도 저절로 생성될 수 있는 멤버 함수들을 말한다. C++ 98에는 기본 생성자, 기본 파괴자, 복사 생성자, 복사 대입 연산자라는 4개의 special member function이 있었다.C++11에는 여기서 이동 생성자, 이동 대입 연산자라는 두 가지 special member function이 더 추가 되었다.class Widget { public: Widget(Widget&& rhs); Widget& operator =(Widget&& rhs); };새로운 두 special member function은 위와 같은 type signature를 갖고 있다. 이 함수들도 조건이 성립하면..
const 멤버 함수는 thread safe수학에서 다항식(polynomial)을 나타내는 클래스를 만든다고 해보자. 다항식을 나타내는 클래스니까 다항식의 근(roots)들을 구하는 함수가 있으면 굉장히 편리할 것이다. 근을 구하는 함수는 다항식 자체에는 어떤 영향을 끼치는게 아니니 이건 const 멤버 함수로 선언하자.class Polynomial { public: using RootsType = std::vector; RootsType roots() const; };다항식의 근을 계산하는 함수는 비용이 굉장히 비쌀 것이다. 근은 한 번만 계산하면 바뀌는 값이 아니므로 한 번 계산한 다음에는 값을 캐시(cache)해두고 바로바로 불러오는 방식으로 하면 성능이 훨씬 개선된다.class Polynomial..
const_iterator를 쓰자!STL에서 const_iterator는 포인터로 따지면 const T* 와 같다. 즉 iterator가 가리키는 대상을 수정하지 못함(데이터의 상수성)을 의미하는 것이다. Effective C++에서도 다룬 내용이지만 const는 많이 쓸 수록 좋다. 해당 iterator가 가리키는 대상을 꼭 수정할 필요가 없다면 const_iterator를 사용하는 편이 더 안정적인 코드가 될 것이다.C++ 98에서는 써먹을 게 못 됐다const를 쓸 수 있으면 가능한 한 많이 쓰는 게 좋다는 일반적인 원칙에도 불구하고 C++ 98에서는 const_iterator를 쓰기가 힘들었다. 사용하기도 굉장히 불편했고 기능이 제대로 구현되어 있다고 보기 힘든 상태였기 때문이다. 예제를 통해 C..
오버라이딩엔 overrideC++에서 오버라이딩은 굉장히 흔히 쓰이는 개념이다. 상속받은 클래스가 기반 클래스의 함수를 재정의하는 것을 오버라이딩이라고 하는데, 오버라이딩이 일어나기 위해선 몇 가지 조건이 필요하다.기반 클래스의 함수가 가상 함수(virtual function)이어야만 한다.기반 함수와 상속된 함수(derived function)의 이름이 반드시 같아야한다(소멸자 제외).기반 함수와 상속된 함수의 매개변수 타입도 반드시 같아야한다.기반 함수와 상속된 함수의 상수성(constness)도 반드시 같아야한다.기반 함수와 상속된 함수의 리턴 타입 및 예외 지정(exception specification)이 서로 호환되어야만 한다.굉장히 당연해보이는 조건들이다. 여기에 C++ 11에서는 한 가지 ..
정의하지 않기보단 deleted functionC++에서는 정의하지않아도 자동으로 생성되는 몇몇 함수들이 있다. 그 대표적인게 생성자 소멸자 시리즈들과 복사에 관련된 녀석들인데, 이 놈들은 정의하지 않아도 자동으로 생성되어버리기 때문에 때로는 골치아픈 문제를 야기한다.C++98에서는 이런 녀석들을 의도적으로 사용하지 못하게 막고 싶을 때, 이걸 private으로 선언하고 정의는 하지 않는 방법을 많이 택했다. 대표적으로 표준 라이브러리의 stream과 관련된 것들이 있다. 입,출력 스트림과 관련된 클래스를 복사한다는 것 자체가 논리적으로 말이 안 되기 때문에 C++98에서는 이들 클래스의 조상 클래스인 basic_ios를 다음과 같이 정의했다.template class basic_ios : public ..
scoped enum을 쓰자C++ 98의 enum은 범위 제한이 없다. 한 마디로 말해 아래와 같은 상황이 벌어진다는 것이다.enum Color { BLACK, WHITE, RED }; auto WHITE = false; // WHITE가 이미 선언되었다!분명 WHITE는 Color라는 enum 내부의 값 중 하나임에도 불구하고 C++98의 enum은 범위에 제한이 없어 모든 이름 공간에서 선언이 되어버린다. 그래서 C++ 11에서는 scoped enum을 지원한다.enum class Color { BLACK, WHITE, RED}; auto WHITE = false; //가능함 Color c = WHITE; // 불가능! Color c = Color::WHITE; // 가능. 범위를 명시해줘야함. au..
typedef보다는 alias declaration을 써라std::unique_ptr 같은 타입을 생각해보자. STL 컨테이너와 스마트 포인터가 섞이게 되면 타입 이름이 굉장히 길어지게 되고, 이건 코드 가독성을 심하게 훼손한다. C++98까지는 이런 상황에 typedef를 썼다.typedef std::unique_ptr UPtrMapSS;C++11에서는 이런 경우에 typedef대신 alias declaration을 사용한다.using UPtrMapSS = std::unique_ptrtypedef나 alias declaration이나 하는 역할은 완전히 똑같고, 기술적으로도 무슨 차이가 생기거나 하는 건 아니다. 하지만 typedef에 비해 alias declaration이 훨씬 가독성이 좋고, 헷갈리..
0과 NULL보다는 nullptr를 써라0, NULL은 포인터 타입이 아니다. 그래서 C++98에서는 함수 오버로딩에서 포인터를 인자로 받는 함수에 null pointer를 넘길 때 애로사항이 꽃피는 경우가 많았다.void f(int); void f(bool); void f(void*); f(0); //f(void*)가 아니라 f(int)가 호출. f(NULL); //보통 f(int)가 호출됨. f(void*)가 호출될 일은 절대 없다.NULL은 컴파일러마다 조금씩 다르긴 하지만 보통 0L(long 타입의 0) 또는 0으로 선언되어 있다. 그래서 "NULL(null pointer)로 함수를 호출하겠다"라는 프로그래머의 의도와는 다르게 컴파일러는 실제 의미인 0L 또는 0을 인자로 넘긴 것으로 받아들여 f..
객체를 만들 때 ()과 {}를 구분해라C++11에서 객체를 초기화하는 방법은 4가지가 존재한다.int x(0); // ()를 이용한 초기화 int y = 0; // =을 이용한 초기화 int z{ 0 }; // {}를 이용한 초기화 int w = { 0 }; // =과 {}를 이용한 초기화여기서 {}와 = {}는 동일한 의미를 갖기 때문에 사실상 3가지의 방법이 존재한다고 볼 수 있다.uniform initializationC++ 뉴비라면 아래의 두 문장을 헷갈리기 쉽다.Widget w1; // 디폴트 생성자 호출 Widget w2 = w1; // 대입 ㄴㄴ 복사 생성자 호출 w1 = w2; //대입임. 연산자 =이 호출됨.똑같이 =으로 표기되기 때문에 얼핏 대입으로 헷갈릴 수 있지만 Widget w2 ..