헤더 파일시스템
을 사용한다. (C++ 발표일: 1985년, C 발표일: 1972년)모듈시스템
을 사용한다.(Java 발표일: 1995년, C#발표일: 2000년)헤더파일시스템
은 내가 사용하고자 하는 기능이 들어있는 파일을 그대로 복사 붙여넣기 하는 방식으로 작동하기에 내가 원하지 않는 기능까지 같이 컴파일된다.모듈시스템
은 내가 사용하고자 하는 기능만 그대로 가져오기에 내가 원하지 않는 기능까지 같이 컴파일되지 않는다.위에 말했듯 헤더파일시스템
은 기본적으로 복붙하는 방식으로 작동한다.
실제 C++에서 #include "헤더파일.h”
구문의 주된 역할은 내가 지정한 헤더파일(사실 헤더파일이 아니어도 상관 없음)을 복붙해 오라는 뜻이다.
이를 도식화해보자면 내가 파일과 코드 구성을 다음과 같이 했을 때,
실제 전처리를 진행하고 나면 실제 컴파일해야 하는 소스코드의 양은 다음과 같다.
(사실 C++에선 전처리 이후 실제 컴파일되는 파일들은 .cpp
파일들밖에 없어서 A.h
, B.h
, C.h
파일들은 컴파일되지 않는다. 위 도식은 이해를 쉽게 하기 위해 임의로 만들어낸 것임을 이해해 주길 바란다. 만약 해당 파일들도 A.cpp
, B.cpp
, C.cpp
처럼 구현부가 나눠져 있다면 모두 컴파일 해야한다)
헤더파일의 inlcude 계층이 깊어질수록 실질적으로 컴파일해야 할 코드의 양은 O(n^2)
만큼 늘어난다고 볼 수 있다.
코드가 간단하면 상관 없겠지만 만약 사용하려는 코드가 매우 길다면 이는 큰 부담이 된다.
우리가 흔히 사용하는 stdio.h
만 하더라도 헤더파일만 2000줄이 넘는다(Visual Studio 기준). 만약 여기에 템플릿까지 포함된다면 컴파일시간은 종잡을 수 없이 길어질 것이다.
특히 위처럼 include계층이 깊은 상황에서 C.h
파일이 수정된다면 어떻게 될까? C.h
가 아주 살짝만 바뀌어도 이를 포함하는 모든 파일들에 대해 컴파일을 다시 해야한다. 그러면 당연히 컴파일이 오래 걸릴 수밖에 없을 것이다.
// C.h 파일
#pragma once
#include<cstdio> // 입출력 헤더
void funcInC() {
printf("Function in C\n");
printf("\n");
}
// B.h 파일
#pragma once
#include<cstdio>
#include "C.h" // C 헤더 추가
void funcInB() {
printf("Function in B\n");
printf("\n");
}
// A.h 파일
#pragma once
#include<cstdio>
#include "B.h" // B헤더 추가
void funcInA() {
printf("Function in A\n");
printf("\n");
}
// main.cpp 파일
#include "A.h" // A헤더만 추가해도 B, C헤더의 기능들을 모두 활용 가능하다.
int main() {
funcInA(); // A.h의 함수
funcInB(); // B.h의 함수
funcInC(); // C.h의 함수
// main.cpp에서 직접 추가하지 않은 파일들까지 전부 실행 가능하다.
printf("cstdio is also included\n");
// cstdio 헤더의 함수도 직접 includ되지 않았지만 쓸 수 있다.
return 0;
}
// C.cs
using namespaceC;
namespace namespaceC
{
internal class C
{
internal static void print()
{
Console.WriteLine("function in C class\n");
}
}
}
// B.cs
using namespaceC; // 사용하고자 하는 C.cs의 네임스페이스 추가
namespace namespaceB
{
internal class B
{
internal static void print()
{
Console.WriteLine("function in B class\n");
C.print(); // => 잘 된다.
}
}
}
// A.cs
using namespaceB; // 사용하고자 하는 B.cs의 네임스페이스 추가
namespace namespaceA
{
internal class A
{
internal static void print()
{
Console.WriteLine("function in A class\n");
B.print(); //=> 잘 된다.
//C.print(); //=> 에러가 난다.
}
}
}
// Program.cs
using namespaceA; // 사용하고자 하는 A.cs의 네임스페이스 추가
// 헤더파일 시스템과는 다르게 B.cs, C.cs의 기능을 사용하려면 직접 using 해줘야 한다.
namespace Main
{
class Program
{
static void Main(string[] args)
{
A.print();
//B.print(); //=> 에러가 난다.
//C.print(); //=> 에러가 난다.
}
}
}