목록프로그래밍/EMC++ (31)
INDIES
오버라이딩엔 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 ..
auto의 의도치 않은 타입추론 대처하기item 5에서 auto가 굉장히 다양한 장점을 갖고 있다고 이야기했지만, 가끔씩 auto가 의도치 않은 방향으로 동작할 때가 있다. 가장 간단한 예로 std::vector이 있다.std::vector vb = {true, true, true}; auto vb2 = vb[2]; //vb2의 타입은 bool이겠지?안타깝지만 vb2의 타입은 bool이 아니다. std::vector의 경우 내부적으로 효율성을 위해 1비트당 하나의 값을 저장한다(bool은 1비트면 저장 가능하므로). 따라서 vb[2]같은 경우 어떤 지점에서 2비트 뒤의 주소 같은 건 가져올 수 없으므로 bool& 타입을 리턴하는 것이 아니라 std::vector::reference 타입을 리턴한다. 이 ..
auto를 쓰자!auto의 장점들auto를 이용하는 게 명시적으로 타입을 서명해주는 것보다 나은 점이 굉장히 많다.간결함auto를 쓰면 길고 복잡한 타입을 간단히 표기할 수 있으니 손가락도 덜 아프고 보기도 좋다. 특히 STL을 쓰다보면 iterator를 써야하는 경우가 굉장히 많은데 iterator 관련 타입들은 하나같이 타입 이름이 길어서 일일히 쓰다보면 지치기도 지치고 보기도 안 좋다. 이럴 때 auto를 이용하면 코드가 훨씬 깔끔해진다.template void f(It a) { typename std::iterator_traits::value_type currValue = *b; //극혐 }해당 반복자가 가리키는 값의 타입을 얻어 오기 위해선 위 코드처럼 굉장히 장황한 코드를 써야한다. 하지만 a..
추론된 타입 보는 법요즘 IDE는 워낙 성능이 좋아 auto, template등에서 타입이 어떻게 추론됐는지 궁금하다면 그냥 그 위에 마우스만 갖다대도 알려준다(visual studio).하지만 위 스크린샷에서도 볼 수 있듯이 std::vector가 std::vector라고 뜬다. 일반적으로 잘 쓰지 않는 형태의 타입으로 보여줄 때가 많기 때문에 어떤 타입의 경우 길이가 너무 길어져서 보기 힘들어진다.그럴 땐 아래 몇 가지 방법을 활용해보자.컴파일러 진단의도적으로 컴파일 에러를 일으켜서 추론된 타입을 확인하는 방법이다.//타입을 확인하기 위한 템플릿 클래스. template class TD; auto k = &v[3]; TD kType;위 코드에서 k의 타입이 궁금해 마우스를 올렸다간 아래 사진처럼 심히..
decltype 이해하기decltype의 동작 및 활용decltype은 declared type의 의미로 해당 식이 나타내는 타입을 그대로 돌려준다. 어떤 이름(name)이나 표현식(expression)을 decltype에 집어넣으면 decltype은 해당 이름이나 표현식의 타입을 알려준다.item 1,2에서 다뤘던 auto나 template에서의 타입 추론과 decltype이 다른 점은, decltype은 해당 표현식이나 이름의 타입을 있는 그대로 돌려준다는 점이다.const int i = 0; // decltype(i) => const int // decltype(&i) => const int* const int& ri = i; // decltype(ri) => const int& int f(int ..