2025/03/14 ch.11 C++의 까다롭고 유별난 부분
11.2 헤더 파일
11.2.1 중복 정의
인클루드 가드를 이용하면 중복 정의를 피할 수 있다.
요즘은 #pragma once 지시자를 지원하기 때문에 저렇게 불편하게 안써도 됨
11.2.2 순환 의존
클래스를 참조해야 하지만 헤더 파일을 포함할 수 없는 경우가 있다
A 클래스에서 B 클래스가 필요한데 B클래스에서도 A 클래스가 필요한 경우
두 클래스에 각각의 클래스를 전부 include 하면 순환 의존문제가 발생
이럴 때 전방선언을 하면 된다.
11.2.3 헤더 존재 확인
특정 헤더 파일이 존재하는지 확인하려면 전처리 상수 표현식
__has_include("파일명") 또는 __has_include(<파일명>) 을 사용해야 한다.
해당 헤더 파일이 존재하면 1, 그렇지 않으면 0이 된다.
11.3
C++20 부터 기능 테스트 매크로가 추가됨. 코어 언어 기능 중에서 현재 컴파일러가 지원하는 것들을 확인할 수 있다.
해당 기능이 추가되거나 업데이터 된 연과 월을 나타내는 숫자로 구성됨 YYYYMM
11.4 static 키워드
11.4.1 static 데이터 멤버와 메서드
static 데이터 멤버는 static으로 지정하지 않은 멤버와 달리 객체에 속하지 않는다. 객체의 외부에 단 하나만 존재
특정 객체에 대해 실행할 수도 없고 this 포인터도 없다. const로 지정할 수도 없다
11.4.2 static 링크
함수나 글로벌 변수처럼 C++ 소스 파일마다 정의된 이름은 외부 링크나 내부 링크를 통해 서로 연결됨.
외부 링크로 연결되면 다른 소스 파일에서 이름을 사용할 수 있고, 내부 링크(정적 링크)로 연결되면 같은 파일에서만 사용할 수 있다. 함수나 글로벌 변수는 기본적으로 외부링크. 선언문 앞에 static 키워드 붙이면 내부 링크
전처리기는 소스 파일에 있는 #include 파일을 보고 헤더 파일에 나온 프로토타입을 소스 파일에 추가함
# 일반적인 static 변수<- 선언된 스코프를 벗어나도 선언된 소스 파일 내에서는 자유롭게 쓸 수 있다. 다른 파일에선 사용 불가.
static 대신 익명 네임스페이스를 이용하여 내부 링크가 적용되게 할 수도 있다.
익명 네임스페이스에 속한 항목은 이를 선언한 소스파일 안에서는 어디서든 접근할 수 있지만 다른 소스 파일에서는 접근할 수 없다.
#include <iostream>
// ✅ 익명 네임스페이스 사용
namespace {
int hiddenVar = 100;
void hiddenFunction() {
std::cout << "Inside hiddenFunction: " << hiddenVar << std::endl;
}
}
void accessHidden() {
hiddenFunction(); // ✅ 같은 파일이므로 사용 가능
}
int main() {
hiddenFunction(); // ✅ 사용 가능
accessHidden(); // ✅ 사용 가능
std::cout << "hiddenVar: " << hiddenVar << std::endl; // ✅ 사용 가능
}
개념이 헷갈려서 정리
익명 네임스페이스로 선언만 하면 그 스페이스의 스코프에서 벗어나면
같은 파일 내에서 자유롭게 사용 가능
extern 키워드
어떤 이름을 extern으로 지정하면 컴파일러는 그 변수에 대해 메모리를 할당하지 않는다
정의가 아닌 선언으로 취급.
따라서 그 변수를 정의하는 문장을 따로 작성해야 함.
11.4.3 함수 안의 static 변수
특정한 스코프 안에서만 값을 유지하는 로컬 변수
함수 안에서 static으로 지정한 변수는 그 함수 안에서만 접근할 수 있는 글로벌 변수와 같다
헷갈리니 권장하지는 않음
싱글턴 패턴에서 많이 씀
11.4.4 비 로컬 변수의 초기화 순서
둘다 main()이 시작하기 전에 초기화 된다.
소스 파일에 작성된 순서대로 초기화 됨
다만 여러 소스 파일에 선언된 경우의 초기화 순서는 C++ 표준에서 따로 정해두진 않았음
대부분 상관없겠지만 다른 파일에 있는 비 로컬 변수끼리 의존관계가 있으면 컴파일러마다 동작이 달라질 수 있으니 주의
11.4.5 비 로컬 변수의 소멸 순서
생성된 순서와 반대로 소멸됨. 마찬가지로 여러 소스파일에 있는것들은 표준이 없으므로 정확히 알 수가 없다
11.5 C 유틸리티
가변길이 인수 리스트
요즘은 가변 인수 템플릿을 사용하는 것이 바람직하지만 레거시 코드에서 사용되는 경우가 있으므로 알아두자
printf()와 같이 인수의 갯수가 가변적인것들
매개변수 맨 뒤에 ...을 붙여준다. 인수의 개수와 타입이 다양하다는 뜻
반드시 <cstdarg>에 정의된 매크로로 접근해야 한다.
va_list 타입의 변수를 선언하고 va_start를 호출해서 초기화 함
va_start()의 두번째 매개변수는 반드시 매개변수 리스트의 이름 있는 변수 중에서 오른쪽 끝에 있는 것이어야 함
가변 길이 인수 리스트를 가진 함수라면 반드시 이름 있는 매개변수가 한 개 이상 있어야 함
이 함수는 인수 리스트를 곧바로 <cstudio>에 있는 표준 함수인 vfprintf()로 전달함. vfprintf()를 호출한 결과가 리턴되면 va_end()를 호출해서 가변 길이 인수 리스트에 대한 접근을 종료함.
va_start()를 호출했다면 반드시 이에 대응하는 va_end()를 호출해서 함수의 스택 상태를 일관성 있게 유지해야 함
실제 인수에 직접 접근하고 싶다면 va_arg()를 사용한다. 이 매크로는 va_list와 이를 해석할 타입을 인수로 받는다
근데 인수 리스트의 끝은 명시적으로 지정하지 않고서는 알아낼 방법이 없다..
이러한 C 스타일 가변 길이 인수 리스트를 사용하면 안된다
- 매개변수 개수를 알 수 없다
- 인수의 타입을 알 수 없다
11.5.2 전처리 매크로
C++ 전처리기로 간단한 함수 형태의 매크로를 정의할 수 있음
타입 검사를 하지 않고 이를 호출한 모든 부분을 전처리기가 텍스트 바꾸기를 하듯 교체함. inline 함수외 비슷
매크로는 텍스트를 단순히 교체하므로 잘못하면 원치않은 결과를 얻을 수도 있다
프로그래머가 보는 코드와 디버거가 보는 코드가 달라지므로 디버깅도 까다로워진다.
인라인 함수를 사용하는것이 바람직하다
~620p