C++

35: 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자 게임 캐릭터를 설계하는 상황이다. 캐릭터의 체력이 얼마나 남았는지 나타내는 정수 값을 반환하는 함수를 설계한다. class GameCharacter { public: // 캐릭터의 체력을 반환 // 파생 클래스는 이 함수를 재정의할 수 있다. virtual int healthValue() const; }; healthValue가 순수 가상 함수가 아닌 점을 보아, 기본 알고리즘이 제공된다는 것을 알 수 있다. 너무나 당연한 설계이지만 이것 말고 적당한 다른 방법은 어떤게 있을까? 비가상 인터페이스 관용구를 통한 템플릿 메서드 패턴 가상 함수는 반드시 private 멤버로 두어야 한다는 생각에서 시작되었다. healthValue를 ..
34: 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자 상속은 사실 두 가지로 나뉜다. 하나는 함수 인터페이스의 상속이고, 또 하나는 함수 구현의 상속이다. 멤버 함수의 인터페이스(선언)만을 파생 클래스에 상속받고 싶을 때가 분명히 있다. 어쩔 때는 함수의 인터페이스 및 구현을 모두 상속받고 그 상속받은 구현이 오버라이드가 가능하게 만들었으면 하는 일도 있다. 반대로, 인터페이스와 구현을 상속받되 어떤 것도 오버라이드할 수 없도록 막고 싶은 경우도 있다. class Shape { public: virtual void draw() const = 0; virtual void error(const string& msg); int objectID() const; }; class Rectangle ..
33: 상속된 이름을 숨기는 일은 피하자 int x; void someFunc() { double x; cin >> x; } 안쪽 유효범위에 있는 이름이 바깥쪽 유효범위에 있는 이름을 가리기때문에 실제로 참조하는 x는 전역 변수 x가 아니라 지역 변수 x이다. 컴파일러가 someFunc의 유효범위 안에서 x를 만나면, 일단 그 컴파일러는 자신이 처리하고 있는 유효범위, 즉 지역 유효범위(local scope)를 뒤져서 같은 이름을 가진 것이 있는 가를 알아본다. x가 바로 있기 때문에, 이 이외의 유효범위에 대해서는 더 탐색하지 않는다. 파생 클래스 멤버 함수 안에서 참조하는 문장이 있으면 컴파일러는 이 참조 대상을 바로 찾아낼 수 있다. 기본 클래스에 선언된 것은 파생 클래스가 모두 물려받기 때문이다...
32: public 상속 모형은 반드시 "is-a(...는 ...의 일종이다)"를 따르도록 만들자 클래스 D를 클래스 B로부터 public 상속을 통해 파생시켰다면 C++ 컴파일러에게 이렇게 말한 것과 같다. D 타입으로 만들어진 모든 객체는 또한 B 타입의 객체이지만, 그 반대는 되지 않는다. 다시 말해 B는 D보다 더 일반적인 개념을 나타내며, D는 B보다 더 특수한 개념을 나타낸다고 알리는 것이다. B 타입의 객체가 쓰일 수 있는 곳에는 D 타입의 객체도 쓰일 수 있다고 단정하는 것이다. class Person {}; class Student : public Person {}; void eat(const Person& p); void Study(const Student& s); Person p; S..
31: 파일 사이의 컴파일 의존성을 최대로 줄이자 파일 사이의 컴파일 의존성이 높다면 하나의 파일에 연쇄적으로 컴파일되어 시간이 오래 걸린다. 문제의 핵심은 C++가 인터페이스와 구현을 깔끔하게 분리하는 일에 별로 일가견이 없다는데 있다. C++의 클래스 정의는 클래스 인터페이스만 지정하는 것이 아니라 구현 세부사항까지 상당히 많이 지정하고 있기 때문이다. class Person { Person(const string& name, const Date& birthday, const Address& addr); string name() const; string birthday() const; string address() const; private: string theName; // 구현 세부사항 Data ..
30: 인라인 함수는 미주알고주알 따져서 이해해 두자 인라인 함수는 함수 호출 비용이 면제되는 것외에 다른 이점이 있다. 컴파일러 최적화는 함수 호출이 없는 코드가 연속적으로 이어지는 구간에 적용되도록 설계되었기 때문에, 인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별(context_sepcific) 최적화를 걸기가 용이해진다. 일반적인 함수 호출에는 이런 최적화를 적용하지 않는다. 하지만 인라인 함수도 공짜는 아니다. 목적 코드의 크기가 커진다. 메모리가 제한된 컴퓨터에서 아무 생각 없이 인라인을 남발했다가는 프로그램 크기가 그 기계에서 쓸 수 있는 공간을 넘어버릴 수도 있다. 가상 메모리를 쓰는 환경일지라도 인라인 함수로 인해 부풀려진 코드는 성능의 걸림돌이 되기 쉽다. 페이징 횟수가 늘어..
29: 예외 안정성이 확보되는 그날 위해 싸우고 또 싸우자! class PrettyMenu { public: void changeBackground(istream& imgSrc); // 배경그림을 바꾸는 멤버 함수 private: Mutex mutex; // 이 객체 하나를 위한 뮤텍스 Image* bgImage; // 현재의 배경그림 int imageChanges; // 배경그림이 바뀐 횟수 }; void PrettyMenu::changeBackground(istream& imgSrc) { lock(&mutex); // 뮤텍스 획득 delete bgImage; // 이전의 배경그림을 없앤다. ++imageChanges; // 그림 변경 횟수를 갱신 bgImage = new Image(imgSrc); /..
28: 내부에서 사용하는 객체에 대한 '핸들'을 반환하는 코드는 되도록 피하자 class Point { public: Point(int x, int y); void setX(int newVal); void setY(int newVal); }; struct RectData { Point ulhc; Point lrhc; }; class Rectangle { public: Point& upperLeft() const { return pData->ulhc; } Point& lowerRight() const { return pData->lrhc; } private: tr1::shared_ptr pData; }; 위의 코드는 컴파일은 잘 된다. 하지만 결정적으로 틀렸다. 상수 멤버 함수은 upperLeft와 low..
27: 캐스팅은 절약, 또 절약! 잊지 말자 일단 캐스팅은 C++ 스타일의 캐스팅을 쓰는 것이 바람직하다. 우선, 코드를 읽을 때 알아보기 쉽고, 소스 코드의 어디에서 C++의 타입 시스템이 망가졌는지를 찾아보는 작업이 편해진다. 둘째, 캐스트를 사용한 목적을 좀 더 좁혀서 지정하기 때문에 컴파일러 쪽에서 사용 에러를 진단할 수 있다. 캐스팅이 있으면 이로 런타임에 실행되는 코드가 만들어지는 경우가 정말 적지 않다. int x, y; double d = static_cast(x) / y;// x를 y로 나눈다. 부동소수점 나눗셈 사용 위의 코드를 보면 int 타입의 x를 double 타입으로 캐스팅한 부분에서 코드가 만들어진다. 그것도 거의 항상. 왜냐하면 대부분의 컴퓨터 아키텍처에서 int의 표현구조와..
26: 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자 생성자 혹은 소멸자를 호출하는 타입으로 변수를 정의하면 반드시 물게 되는 비용이 두 개있다. 하나는 생성자가 호출되는 비용이고, 하나는 소멸자가 호출되는 비용이다. 변수가 정의됐으나 사용되지 않은 경우에도 비용이 부과되는데, 이런 비용은 웬만한 경우가 아니면 물고 싶을 생각이 안 들 것이다. 함수 실행도중 예외를 던지는 코드이다. string encryptPassword(const string& password) { string encrypted; // 최소 길이보다 비밀번호가 짧으면 예외를 던진다. if (password.length() < MinimunPasswordLength) { throw logic_error("Password is..
KANTAM
'C++' 카테고리의 글 목록 (3 Page)