C++/Effective C++

45: "호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방! 포인터에는 스마트 포인터로 대신할 수 없는 특징이 있다. 그 중 하나가 암시적 변환을 지원한다는 점이다. class Top {}; class Middle : public Top {}; class Bottom : public Middle {}; Top* pt1 = new Middle; Top* pt2 = new Bottom; const Top* pct2 = pt1; 이런 식의 타입 변환을 사용자 정의 스마트 포인터를 써서 흉내 내려면 무척 까다롭다. template class SmartPtr { public: explicit SmartPtr(T* realPtr); }; SmartPtr pt1 = SmartPtr(new Middle)..
44: 매개변수에 독립적인 코드는 템플릿으로부터 분리시키자 템플릿은 코딩 시간 절약, 코드 중복 회피의 두 마리 토끼를 한번에 잡아 주는 참으로 기막힌 물건이다. 하지만 코드 비대화(code bloat)가 초래될 수 있다. 똑같은 내용의 코드와 데이터가 여러 벌로 중복되어 이진 파일로 구워진다는 뜻이다. 목적 코드가 아주 커진다. 우선적으로 써 볼 수 있는 방법은 공통성 및 가변성 분석(commonality and variability analysis)다. 공통 부분을 다른 곳에 옮기고 , 고유 부분은 원래 위치로 남겨 두는 것이다. 템플릿이 아닌 코드에서는 코드 중복이 눈으로 보이지만, 템플릿 코드에서는 코드 중복이 암시적이다. 소스 코드에는 템플릿이 하나밖에 없기 때문에, 어떤 템플릿이 여러 번 인스..
43: 템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자 몇 개의 회사에 메시지를 전달하는 프로그램을 만드는 상황이라고 가정하자. 어느 회사에 보낼지를 컴파일 도중에 결정할 수 있다면 템플릿 기반의 방법을 쓸 수 있다. class CompanyA { public: void sendCleartext(const std::string& msg); void sendEncrypted(const std::string& msg); }; class CompanyB { public: void sendCleartext(const std::string& msg); void sendEncrypted(const std::string& msg); }; class MsgInfo {}; template clas..
42: typename의 두 가지 의미를 제대로 파악하자 template class Widget; template class Widget; 위의 두 템플릿 선언문에서 class와 typename의 차이점은 무엇일까? 정답은 차이가 없다. 템플릿 매개변수를 선언하는 경우의 class 및 typename은 완전히 같은 의미를 지닌다. 하지만 typename을 사용하지 않으면 안 되는 때가 있다. 이때가 언제인지를 알아보자. 일단 템플릿 안에서 참조할 수 있는 이름의 종류는 두 가지이다. template void print2nd(const C& container) { if (container.size() >= 2) { // 제정신으로 짠 코드가 아니다! C::const_iterator iter(containe..
41: 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부터 객체 지향 프로그래밍의 세계를 회전시키는 축은 명시적 인터페이스(explicit interface)와 런타임 다형성(runtime polymorphism)이다. class Widget { public: Widget(); virtual ~Widget(); virtual std::size_t size() const; virtual void normalize(); void swap(Widget& other); }; void doProcessing(Widget& w) { if (w.size() > 10 && w != someNastyWidget) { Widget temp(w); temp.normalize(); temp.swap(w);..
40: 다중 상속은 심사숙고해서 사용하자 다중 상속(multiple inheritance: MI)하면 바로 머리에 들어와야 하는 사실 중 하나는, 둘 이상의 기본 클래스로부터 똑같은 이름(이를테면 함수, typedef 등)을 물려받을 가능성이 생겨 버린다. 모호성이 생긴다는 것이다. class BorrowableItem { public: void checkOut(); }; class ElectronicGadget { private: bool checkOut() const; }; class MP3Player : public BorrowableItem, public ElectronicGadget {}; MP3Player mp; mp.checkOut(); // 모호성 발생! 위의 checkOut 함수는 C++..
39: private 상속은 심사숙고해서 구사하자 class Person {}; class Student : private Person {}; void eat(const Person& p); Person p; Student s; eat(p); eat(s); // 에러! Student는 Person의 일종이 아니다. Person의 파생 클래스 Student를 public이 아닌 private으로 상속하였다. private 상속은 분명이 is-a를 뜻하지 않는다. 우선 private 상속을 쓰면 어떻게 되는지 알아보자. 클래스 사이의 상속 관계가 private이면 컴파일러는 일반적으로 파생 클래스 객체를 기본 클래스 객체로 변환하지 않는다. 그리고 기본 클래스로부터 물려받은 멤버는 파생 클래스에서 모조리 p..
38: "has-a(...는...를 가짐)" 혹은 "is-implemented-in-terms-of(...는...를 써서 구현됨)"를 모형화할 때는 객체 합성을 사용하자 합성(composition)이란, 어떤 타입의 객체들이 그와 다른 타입의 객체들을 포함하고 있을 경우에 성립하는 그 타입들 사이의 관계를 일컫는다. 포함된 객체들을 모아서 이들을 포함한 다른 객체를 합성한다는 뜻인데, 이를 테면 다음과 같은 경우다. class Address{}; class PhoneNumber{}; class Person { public: private: std::string name; // 이 클래스를 이루는 객체들 Address address; PhoneNumber voiceNumber; PhoneNumber fax..
37: 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지 말자 일단 이번 문제의 원인은 다음과 같다. 가상 함수는 동적으로 바인딩되지만, 기본 매개변수 값은 정적으로 바인딩된다는 것이다. 객체의 정적 타입(static type)은 프로그램 소스 안에 놓는 선언문을 통해 그 객체가 갖는 타입이다. class Shape { public: enum ShapeColor {Red, Green, Blue}; virtual void draw(ShapeColor color = Red) const = 0; }; class Rectangle : public Shape { public: // 기본 매개변수 값이 달라졌다. 큰일 났다! virtual void draw(ShapeColor color = Gre..
36: 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물! D라는 이름의 클래스가 B라는 이름의 클래스로부터 public 상속에 의해 파생되었고, B 클래스에는 mf라는 이름의 public 멤버 함수가 정의되어 있다고 가정하자. class B { public: void mf(); }; class D : public B {}; B나 D, 혹은 mf에 대해 전혀 모르는 상태에서 D 타입의 객체인 x가 다음처럼 있다고 할 때, D x;// x는 D타입으로 생성된 객체 다음과 같이 작성한 코드가, B* pB = &x;// x에 대한 포인터를 얻어낸다. pB->mf();// 이 포인터를 통해 mf를 호출한다. 다음처럼 동작하지 않으면 꽤나 황당할 것이다. D* pD = &x;// x에 대한 포인터..