일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 게임
- reference
- more effective c++
- implicit conversion
- Effective c++
- Smart Pointer
- 오블완
- UE4
- 예외
- 언리얼
- 영화 리뷰
- operator new
- c++
- 참조자
- 메타테이블
- lua
- 루아
- 반복자
- 스마트 포인터
- 다형성
- 티스토리챌린지
- resource management class
- effective stl
- exception
- Vector
- 암시적 변환
- 비교 함수 객체
- virtual function
- 상속
- 영화
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 30. 인라인 함수 본문
728x90
항목 30. 인라인 함수는 미주알 고주알 따져서 이해해 두자.
인라인 함수
- 함수처럼 보이고, 함수처럼 동작하고, 매크로보다 훨씬 안전하고 쓰기 좋다.(항목 2 참조)
- 함수 호출 비용이 면제된다.(함수 호출 시 발생하는 오버헤드 걱정이 없다.)
- 컴파일러가 함수 본문에 대해 문맥별(context-specific) 최적화를 해줄 여지가 높아진다.
- 대체적으로 컴파일러 최적화는 함수 호출이 없는 코드가 연속적으로 이어지는 구간에 적용되도록 설계된다.
- 인라인 함수는 함수 호출이 없는 것과 마찬가지로 인식되므로 최적화될 확률이 높다.
- 실제로 대부분의 컴파일러는 아웃라인(outline) 함수 호출(일반적인 함수 호출)에 대해서는 이런 최적화를 적용하지 않는다.
인라인 함수와 프로그램 크기
- 인라인 함수 아이디어는 함수 호출문을 그 본문으로 바꿔치기하자는, 단순한 방식이다.
- 대부분의 경우 인라인을 사용하면 목적 코드의 크기가 커진다.
- 메모리 제한 컴퓨터에서는 인라인을 남발할 시 프로그램 크기가 그 기계 허용 공간을 초과할 수 있다.
- 가상 메모리를 사용하는 환경이라 메모리 제한에 대한 걱정이 없는 상황이라 쳐도, 수행 성능 하락은 피할 수 없다.
- 페이징 횟수 증가
- 명령어 캐시 적중률 하락
- 본문 길이가 굉장히 짧은 인라인 함수의 경우
- 교체되는 인라인 함수 본문의 크기가 함수 호출문에 대해 만들어지는 코드보다 작을 수도 있다.
- 이 경우 인라인 함수를 사용하면 되려 프로그램 크기가 작아진다!
인라인 정의 방법
- 암시적 방법
- 클래스 선언부에 함수를 바로 정의해 넣으면 인라인으로 취급한다.
-
class Person { public: ... int age() const { return theAge; } // 암시적 인라인 요청 ... private: int theAge; };
- 대부분 멤버 함수겠지만, 프랜드 함수도 클래스 내부에서 정의될 수 있는데, 이 경우도 마찬가지이다.(항목 46 참조)
- 명시적 방법
- 함수 선언부 혹은 정의부 앞에 inline 키워드를 붙인다.
-
template<typenameT> inline const T& std::max(const T& a, const T& b) // 명시적 인라인 요청 { return a< b ? b : a; }
인라인 함수와 템플릿은 대개 헤더 파일 안에 정의한다
- 대부분의 빌드 환경에서 인라인을 컴파일 타임에 수행한다.
- 그러므로 인라인 함수는 대체적으로 헤더 파일에 인라인 함수가 있는 것이 맞다.
- 인라인 함수 호출을 함수 본문으로 바꿔치기 할 때 컴파일러가 함수 형태를 알아야 하기 때문이다.
- 템플릿을 인스턴스로 만드는 작업은 컴파일 타임에 수행한다.
- 그러므로 템플릿은 대체적으로 헤더 파일에 있는 것이 옳다.
- 템플릿을 사용하는 부분에서 해당 템플릿을 인스턴스로 만들려면 컴파일러가 템플릿의 형태를 알아야 하기 때문이다.
- 하지만, 템플릿 인스턴스와와 인라인은 서로 전혀 관련이 없다.
- 즉, 함수 템플릿이 반드시 인라인 함수여야 할 인과관계는 없다.
- 함수 템플릿에 인라인을 사용할지는 비용을 계산해서 처리해야 할 문제이다.
인라인은 컴파일러가 무시할 수 있는 요청이다.
- 인라인 함수라도 복잡한 함수는 절대 인라인 확장의 대상이 아니다.
- 루프가 들어 있다거나 재귀함수인 경우
- 가상 함수 호출은 인라인 해 주지 않는다.
- virtual의 의미는 "어떤 함수를 호출할지 결정하는 작업을 런타임에 결정한다"이다.
- inline의 의미는 "함수 호출 위치에 호출된 함수를 끼워넣는 작업을 프로그램 실행 전에 한다."이다.
- 서로간의 의미가 상충되므로 인라인 요청이 무시되는 것.
- 인라인 함수가 실제로 인라인 되는가는 컴파일러에 달려 있다.
- 대부분의 컴파일러는 인라인 실패 시 경고 메세지를 띄워주는 설정이 가능하다.(항목 53 참조)
완벽한 인라인 조건이라도 인라인 되지 않는 상황
- 인라인 함수의 주소를 취하는 코드가 있을 경우
- 있지도 않은 함수의 주소를 취할 수는 없기 때문이다.
- 인라인 함수를 함수 포인터로 호출하는 경우
-
inline void f() { ... } // 컴파일러가 반드시 인라인 해준다고 가정해보자. void (*pf)() = f; // f를 가리키는 함수 포인터 ... f(); // 인라인 된다. pf(); // 인라인 되지 않는다.
- 인라인 되지 않는 인라인 함수 문제
- 사용자가 사용하지 않더라도, 컴파일러가 함수 포인터를 필요로 하는 경우가 있을 수 있다.
- 예를 들면) 컴파일러가 어떤 객체의 생성과 소멸 상황에서 생성자 / 소멸자의 함수 포인터를 요구한다.
-
생성자 / 소멸자는 인라인되기 좋지 않다.
class Base
{
public:
...
private:
string bm1, bm2;
};
classs Derived : public Base
{
public:
Derived() {} // Derived의 생성자가 비어 있다? 정말 비어있을까?
...
private:
string dm1, dm2, dm3;
};
- 위 코드만 보면 Derived의 생성자는 아무 코드도 없으니까 인라인 하기 딱 좋다고 오해할 수 있다.
- 하지만 C++은 객체 생성, 소멸 시 반드시 이루어져야 하는 보장이 존재한다.
- 생성자, 소멸자가 호출되며, 데이터 멤버들을 생성하고 소멸하는 등 보이지 않는 보장들.
- 이는 컴파일러가 알아서 생성하는 코드가 존재함을 의미한다.
- 아마도 컴파일러는 이런 식의 코드를 만들어낼 것이다.
-
Derived:Derived() { Base::Base(); // dm1의 생성과 예외 처리 try { dm1.string::string(); } catch (...) { Base::~Base(); throw; } // dm2의 생성과 예외 처리 try { dm2.string::string(); } catch (...) { dm1.string::~string(); Base::~Base(); throw; } // dm3의 생성과 예외 처리 try { dm3.string::string(); } catch (...) { dm1.string::~string(); dm2.string::~string(); Base::~Base(); throw; } }
- 즉 비어있어 보여도 생각보다 비대하고, 상속 트리가 복잡할수록 더더욱 비대할 것이므로 인라인으론 적합하지 않다.
라이브러리 설계 시 인라인 함수
- 라이브러리 설계 시 함수를 인라인 선언하면 바이너리 업그레이드를 제공할 수 없다.
- 라이브러리 업그레이드 시 컴파일을 다시 해야 한다.
- 인라인이 아니라면 링크 과정만 다시 해주면 된다.
인라인 함수에 대한 디버깅 문제
- 대부분의 디버거가 디버깅하기 곤란해한다.
- 있지도 않은 함수에 중단점을 어떻게??
인라인 함수 선언 전략
- 우선 아무것도 인라인하지 않는다.
- 꼭 인라인 해야 하는 함수(항목 46 참조) 나 정말 단순한 함수에 한해서만 인라인하는 것으로 시작한다.
- 디버깅하고 싶은 부분에서 디버거를 제대로 쓸 수 있도록 만들어 두고, 정말 필요할 때만 인라인 함수화 한다.
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 32. public 상속은 "is-a" (0) | 2024.06.28 |
---|---|
[Effective C++] 31. 컴파일 의존성 줄이기 (0) | 2024.06.27 |
[Effective C++] 29. 예외 안전성 (0) | 2024.06.25 |
[Effective C++] 28. 클래스 내부 private 객체에 대한 핸들 리턴 문제 (0) | 2024.06.24 |
[Effective C++] 27. 캐스팅 (0) | 2024.06.21 |
Comments