왜 컴파일이 오래걸릴까요?
컴파일러 문제일 수 있는데, 컴파일러가 옛날 버전이거나 설치가 잘못되었거나, 또 컴퓨터가 구식이어서 일 수 있습니다. 이런 문제는 제가 도와드릴순 없습니다. 하지만, 컴파일 하려는 프로그램의 설계가 형편없어서 컴파일러가 컴파일할 때 수백개의 헤더파일과 수만라인의 코드를 검사해야하는 경우도 있습니다. 원칙적으로는 이런 문제는 피할 수 있습니다. 만약 이 문제가 사용중이신 라이브러리 공급업체의 설계에 있다면 할 수 있는것이 거의 없습니다 (라이브러리나 공급업체를 변경하는 것을 제외하곤), 하지만 수정 후 재컴파일을 최소화하기 위해 코드의 구조를 바꿀 수 있습니다. 재컴파일을 최소하 하는 디자인은 전형적으로 더 나은, 더 유지보수에 유용하며, 관계의 분리에 더 좋은 모습을 보여주기 때문에 설계됩니다.
전형적인 객체지향 프로그램을 예제로 생각해봅시다 :
class Shape {
public: // interface to users of Shapes
virtual void draw() const;
virtual void rotate(int degrees);
// ...
protected: // common data (for implementers of Shapes)
Point center;
Color col;
// ...
};
class Circle : public Shape {
public:
void draw() const;
void rotate(int) { }
// ...
protected:
int radius;
// ...
};
class Triangle : public Shape {
public:
void draw() const;
void rotate(int);
// ...
protected:
Point a, b, c;
// ...
};
이 생각은 사용자가 Shape의 public 인터페이스로 모양을 처리하고, 파생 클래스들의 구현자가 protected 멤버로 설정된 구현체의 외형을 공유하는 것입니다.
이 보기에 간단한 생각에는 3가지 심각한 문제가 있습니다.
모든 파생클래스에 도움이 되는 구현체의 공유된 외형을 정의하기가 쉽지 않습니다. 이러한 이유로는, protected 멤버의 집합이 아마도 public 인터페이스보다 훨씬 더 종종 변경되어야 할 수 있기 때문입니다. 예를 들자면, 'center' 모든 도형들에 유효한 개념이 거의 틀림없더라도, 삼각형의 'center'의 위치를 유지해야 하는것은 상당히 귀찮은 일입니다. 누군가 관심을 가질 때에만 'center'를 계산하는 것이 더 합리적 입니다.
protected 멤버가 Shape들의 사용자가 의존하지 않아도 되는 구현 세부 정보에 의존하게 됩니다. 예를 들자면, Shape를 사용한 많은 코드 (거의?)들이 "color"의 정의와 논리적으로 독립적이게 될 것이고, 아직도 Shape 정의에 있는 색깔의 존재가 os에 색 개념을 정의한 헤더 파일들의 컴파일을 필요로 할 것입니다.
protected 부분에 있는 무언가가 변경될 경우, Shape의 사용자는 재컴파일을 해야하고, 파생 클래스들의 구현자들만 protected 멤버에 접근할 수 있습니다.
그러므로, 사용자 인터페이스의 역할을 하는 기본 클래스에 있는 "구현자에게 유용한 정보"의 존재는 구현 불안전성의 근원이며, 구현 정보가 변경될 때 쓸대없이 사용자 코드를 재컴파일 하며, 헤더파일을 사용자 코드에 과도하게 포함시키게 합니다("구현자에게 유용한 정보"가 이 헤더파일들을 필요로하기 때문에). 이 문제는 "brittle base class problem." (Fragile base class로 더 잘알려져있음) 으로 알려져 있습니다. 1
이 문제의 확실한 해결책은 사용자에게 인터페이스로 사용된 클래스의 "구현자에게 유용한 정보"를 제거하는것입니다. 그러니까, 인터페이스를 만들때는 순수 인터페이스를 만들어야 합니다. 즉, 추상클래스로 인터페이스를 표현하려면:
class Shape {
public: // interface to users of Shapes
virtual void draw() const = 0;
virtual void rotate(int degrees) = 0;
virtual Point center() const = 0;
// ...
// no data
};
class Circle : public Shape {
public:
void draw() const;
void rotate(int) { }
Point center() const { return cent; }
// ...
protected:
Point cent;
Color col;
int radius;
// ...
};
class Triangle : public Shape {
public:
void draw() const;
void rotate(int);
Point center() const;
// ...
protected:
Color col;
Point a, b, c;
// ...
};
이렇게 하여 사용자는 파생 클래스 구현이 변경되는것에 대해 영향을 받지 않게 됩니다. 저는 이 기술이 규모의 정도에 따라 빌드 시간을 줄이는것을 봤습니다.
그런데, 정말로 모든 파생클래스에 공통된 정보가 있다면 (아니면 간단하게 몇개의 파생 클래스만 그런다면)? 간단하게 그 정보를 클래스로 만들고, 구현 클래스를 파생하면 됩니다.
class Shape {
public: // interface to users of Shapes
virtual void draw() const = 0;
virtual void rotate(int degrees) = 0;
virtual Point center() const = 0;
// ...
// no data
};
struct Common {
Color col;
// ...
};
class Circle : public Shape, protected Common {
public:
void draw() const;
void rotate(int) { }
Point center() const { return cent; }
// ...
protected:
Point cent;
int radius;
};
class Triangle : public Shape, protected Common {
public:
void draw() const;
void rotate(int);
Point center() const;
// ...
protected:
Point a, b, c;
};
- 위의 코드에서 protected 멤버, 괜히 중앙 위치랑 색깔을 주려다가 오히려 필요없는데도 손절도 못하고 필요도 없는데 괜히 protected 멤버 건드리면 안쓰는것도 다시 컴파일함 [본문으로]
'번역 > Bjarne Stroustrup's C++ Style and Technique FAQ' 카테고리의 다른 글
Why are member functions not virtual by default? (0) | 2019.02.26 |
---|---|
Why do I have to put the data in my class declarations? (6) | 2019.01.31 |
Why doesn't my constructor work right? (4) | 2019.01.15 |
Does "friend" violate encapsulation? (0) | 2018.12.19 |
Why isn't the destructor called at the end of scope? (2) | 2018.08.13 |
댓글