이펙티브 C++
https://www.yes24.com/Product/Goods/17525589
페이지 210p ~ 220p
이펙티브 C++(210p ~ 220p)
항목 30: 인라인 함수는 미주알고주알 따져서 이해해 두자
- 인라인 함수는 함수처럼 보이고 함수처럼 동작하는데다가, 매크로보다 훨씬 안전하고 쓰기 좋다.
- 인라인 함수를 사용하면 함수 호출 비용이 면제된다.
- 대체적으로 컴파일러 최적화는 함수 호출이 없는 코드가 연속적으로 이어지는 구간에 적용되도록 설계되었기 때문에, 인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해진다.
- 반대로 인라인 함수를 사용했을 때의 단점은 다음과 같다.
- 인라인 함수를 사용한 함수의 호출문은 함수의 본문으로 바꿔치기되기 때문에 목적 코드의 크기가 커지게 된다.
- 가상 메모리를 쓰는 환경이라도 인라인 함수로 인해 부풀려진 코드는 페이징 횟수, 명령어 캐시 적중률에 영향을 줄 가능성이 높아진다.
- 이러한 특징 때문에 본문 길이가 짧은 인라인 함수는 목적 코드의 크기도 작아지며 명령어 캐시 적중률도 높아진다.
- 인라인 함수는 컴파일러에 대해 요청을 하는 것이기 때문에
inline
을 붙이지 않아도 눈치껏 되는 경우도 있고, 명시적으로 할 수도 있다.
암시적인 인라인 요청 방법
class Person {
public:
int age() const { return theAge; }
private:
int theAge;
}
- 클래스 정의(클래스 선언이라고 해도 무방) 안에 함수를 바로 정의해 넣으면 컴파일러는 해당 함수를 인라인 후보로 점찍는다.
명시적인 인라인 요청 방법
template<typename T>
inline const T& std::max(const T&a, const T&b) { return a < b ? b : a; }
- 함수 정의 앞에
inline
키워드를 붙이면 된다. - 인라인 함수는 대체적으로 헤더 파일에 들어 있어야 하는게 맞다. 대부분의 빌드 환경에서 인라인을 컴파일 도중에 수행하기 때문이다.
- 템플릿 역시 대체적으로 헤더 파일에 들어 있어야 맞다. 템플릿이 사용되는 부분에서 해당 템플릿을 인스턴스로 만들려면 그것이 어떻게 생겼는지 컴파일러가 알아야 하기 때문이다.
- 인라인은 컴파일러가 무시할 수 있는 요청인데, 다음과 같은 경우에는 명시적으로 인라인 요청을 해도 컴파일러가 무시한다.
- 컴파일러 입장에서 보기에 복잡한 함수는 절대로 인라인 확장의 대상에 넣지 않는다.(루프, 재귀 등)
- 간단한 함수라도 가상 함수 호출은 절대로 인라인 확장의 대상에 넣지 않는다.
- virtual의 의미는 ‘어떤 함수를 호출할지 결정하는 작업은 실행 중에 한다’이다.
- 확실한 인라인 함수도 어떻게 호출하는지에 따라 인라인이 되기도 하고 안 되기도 한다.
- 함수 포인터가 인라인 함수의 주소를 취하고 있다면, 컴파일러는 이 코드를 위해 함수 본문을 만들 수 밖에 없다.
- 함수 포인터를 전혀 사용하지 않아도 컴파일러는 인라인으로 선언된 생성자 및 소멸자에 대해 함수 본문을 만들 수도 있다.
- 어떤 배열의 원소가 객체일 경우 배열을 구성하는 객체들을 생성하고 소멸시킬 때 생성자/소멸자의 함수 포인터를 얻어내려면 함수 본문이 반드시 필요하다.
- 생성자와 소멸자는 인라인하기에 그리 좋은 함수가 아니다.
class Base {
public:
private:
std::string bm1, bm2;
}
class Derived: public Base {
public:
Derived() {}
private:
std::string dm1, dm2, dm3;
}
- 어떤 객체가 생성되면, 그 객체의 기본 클래스 부분과 데이터 멤버들이 자동으로 생성되며, 그 객체가 소멸될 때 이에 반대되는 순서(파생 클래스부터)로 소멸 과정이 이루어지는 것은 C++에서 보장하는 부분이다.
- 또한, 객체 생성 도중 예외가 발생해도 이미 생성이 완료된 부분은 말끔히 소멸되는 것도 보장한다.
- 따라서 위의 코드는
Derived
의 생성자가 인라인 함수처럼 작동할 것이라 생각할 수 있지만, 기본 클래스 부분을 생성하는 과정을 처리하기 위해 함수 본문을 컴파일러가 만들 수 있다.Base
생성자를 인라인으로 만들면, 생성자에 삽입되어 있던 코드도 전부 파생 클래스의 생성자에 끼어들어갈 것이다.
항목 31: 파일 사이의 컴파일 의존성을 최대로 줄이자
- C++는 인터페이스와 구현을 깔끔하게 분리하는 일에 별로 일가견이 없다.
- C++의 클래스 정의는 클래스 인터페이스만 지정하는 것이 아니라 구현 세부사항까지 상당히 많이 지정하고 있다.
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate const;
std::string address() const;
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
- 위의 코드만 가지고는 Person 클래스를 컴파일 할 수 없다.
- Person 구현의 세부사항이 필요하다.
- string, Date, Address가 어떻게 정의됐는지를 모르면 컴파일 자체가 불가능하다.
- 따라서 #include 지시자를 통해 정의된 정보들을 가져와야 한다.
- #include는 Person을 정의한 파일과 다른 헤더 파일들 사이에 컴파일 의존성을 엮어 버린다.
- 이렇게 되면 include 처리한 헤더 파일이 바뀌거나, 혹은 연관된 헤더 파일이 바뀌더라도 컴파일러에게 영향을 준다.
- 그래서 전방 선언을 통해 구현 세부 사항을 따로 떼어서 지정하기 위해 다음과 같은 시도를 생각해볼 수 있다.
namespace std {
class string;
}
class Date;
class Address;
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate const;
std::string address() const;
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
- 이런 코드가 정상적으로 작동한다면 Person 클래스의 인터페이스가 변경되었을 경우에만 컴파일을 다시 하면 될 것이다. 하지만 이 코드는 정상적으로 작동하지 않는다.
- string은 클래스가 아니라 typedef로 정의한 타입동의어다. 따라서 string은 전방 선언을 할 수 없다.
- 하지만 string과 같은 표준 라이브러리의 일부를 직접 선언할 일은 거의 없을 것이기 때문에 헤더 파일만 잘 포함하면 큰 문제가 되지는 않을 것이다.
- 다른 문제는 컴파일러가 컴파일 도중에 객체들의 크기를 전부 알아야 한다는 데 있다.
- string은 클래스가 아니라 typedef로 정의한 타입동의어다. 따라서 string은 전방 선언을 할 수 없다.
int main()
{
int x;
Person p(params);
}
- 컴파일러는 x의 정의문을 만나면 일단 int 하나를 담을 충분한 공간을 할당해야 한다.
- 그러나 p의 정의문을 만나면 Person 하나를 담을 공간을 할당해야 하는데, Person 객체 하나의 크기가 얼마인지 컴파일러가 알 수 있는 방법은 정의된 정보를 보는 방법 말고 없다.
- 스몰토크나 자바의 경우에서는 다음과 같은 방식으로 처리한다. 따라서 이런 고민은 문제가 되지 않는다.(C++ 양식으로 작성했지만, 포인터를 담을 공간을 할당한다는 개념이다.)
int main()
{
int x;
Person* p;
}
- 이렇게 포인터 뒤에 실제 객체 구현부 숨기기는 C++에서도 할 수 있다.
메모
항목 30: 인라인 함수는 미주알고주알 따져서 이해해 두자
- 함수 인라인은 작고, 자주 호출되는 함수에 대해서만 하는 것으로 묶어두자. 이렇게 하면 디버깅 및 라이브러리의 바이너리 업그레이드가 용이해지고, 자칫 생길 수 있는 코드 부풀림 현상이 최소화되며, 프로그램의 속력이 더 빨라질 수 있는 여지가 최고로 많아진다.
- 함수 템플릿이 대개 헤더 파일에 들어간다는 일반적인 부분만 생걱해서 이들을 inline으로 선언하면 안 된다.
'책 > 이펙티브 C++' 카테고리의 다른 글
이펙티브 C++(240p ~ 250p) (0) | 2024.01.30 |
---|---|
이펙티브 C++(230p ~ 239p) (0) | 2024.01.29 |
이펙티브 C++(201p ~ 210p) (0) | 2024.01.25 |
이펙티브 C++(191p ~ 200p) (0) | 2024.01.25 |
이펙티브 C++(183p ~ 190p) (1) | 2024.01.23 |