C++ 선언 vs 정의: 호출에 본문이 꼭 위에 있어야 할까? (Tour 3.1)

한 줄 답: C++에서 선언은 컴파일러에게 이름과 타입을 알려 호출을 가능하게 하고, 정의는 실제 동작 본문을 제공하여 인터페이스와 구현의 분리를 이룹니다.

이 글은 A Tour of C++ 3.1 Introduction 학습노트를 기준으로 정리한 내용입니다. 3.2 헤더나 모듈에 대한 상세 설명은 다루지 않고, 선언과 정의의 기본 개념에만 집중합니다.

선언(Declaration)과 정의(Definition)는 무엇이 다릅니까?

한 줄 답: 선언은 함수의 이름과 타입 등 사용에 필요한 최소한의 인터페이스를 알리고, 정의는 실제 동작하는 본문을 구현합니다.

C++ 프로그램은 여러 함수, 사용자 정의 타입, 클래스 계층, 템플릿 등 독립적으로 개발된 수많은 부분(part)으로 구성됩니다. 이런 구조에서 핵심이 되는 것은 각 부분 간의 상호작용을 명확하게 정의하는 일이며, 여기서 가장 기본이 되는 원칙이 인터페이스(Interface)와 구현(Implementation)의 분리입니다.

선언(Declaration)은 함수나 타입을 사용하는 데 필요한 최소한의 정보 — 이름, 매개변수 타입, 반환형 등 — 를 컴파일러에게 알리는 역할을 합니다. 반면 정의(Definition)는 중괄호({ }) 안에 들어가는 실제 실행 본문이나 클래스 멤버의 구체적인 구현을 담습니다. 학습노트의 예제로 인터페이스 부분만 먼저 살펴보면 다음과 같습니다.

// 인터페이스 (선언)
double calculate_tax(double amount, double rate);
double square(double x);

class Rectangle {
public:
    Rectangle(double w, double h);
    double area() const;
private:
    double width;
    double height;
};

여기까지는 함수의 매개변수 타입과 반환형, 클래스의 멤버 목록만 알려줄 뿐 실제 계산 로직이나 생성자 본문은 아직 어디에도 없습니다. 그럼에도 이 정보만으로 컴파일러는 이후 호출 코드를 검사할 수 있습니다.

선언과 정의의 관계

선언과 정의의 관계

함수를 쓰려면 정의까지 먼저 있어야 합니까?

한 줄 답: 아닙니다. 컴파일러는 선언(인터페이스)만 먼저 확인하면 매개변수를 검사하고 호출 코드를 정상적으로 컴파일할 수 있습니다.

함수를 호출하는 코드보다 위쪽에 실제 본문(정의) 전체가 있어야 하는 것은 아닙니다. 이름과 매개변수 타입, 반환형을 알리는 선언만 먼저 보이면, 컴파일러는 호출 시 전달하는 인자의 개수와 타입이 맞는지 컴파일 타임에 검사할 수 있습니다. 앞서 선언한 calculate_tax, square, Rectangle을 그대로 사용하는 코드는 다음과 같습니다.

int main() {
    Rectangle rect{10.0, 5.0};
    std::cout << "Area: " << rect.area() << '\n';
    std::cout << "Tax: " << calculate_tax(100.0, 0.1) << '\n';
    std::cout << "square(4.0) = " << square(4.0) << '\n';
}

// 구현 (정의) — main() 아래에 위치해도 문제없음
double calculate_tax(double amount, double rate) {
    return amount * rate;
}
double square(double x) {
    return x * x;
}
Rectangle::Rectangle(double w, double h) : width{w}, height{h} {}
double Rectangle::area() const { return width * height; }

main()calculate_taxsquare, Rectangle::area를 호출하는 시점에는 이들의 정의가 코드 어디에도 등장하지 않았습니다. 그래도 앞서 본 선언이 이미 있었기 때문에 컴파일러는 호출부를 문제없이 컴파일하며, 정의는 파일 아래쪽이나 별도의 소스 파일에 있어도 무방합니다.

선언만으로 호출 코드를 컴파일하는 과정

선언만으로 호출 코드를 컴파일하는 과정

인터페이스와 구현을 나누면 유지보수에 어떤 이점이 있습니까?

한 줄 답: 인터페이스(선언)가 유지된다면, 내부 구현(정의)이 바뀌거나 최적화되더라도 이를 호출하는 쪽의 코드는 수정할 필요가 없습니다.

사용자는 calculate_taxRectangle::area의 내부 계산 방식을 몰라도, 선언에 나온 이름과 매개변수 타입, 반환형만 보고 호출 코드를 작성할 수 있습니다. 나중에 구현 쪽에서 계산 로직을 바꾸거나 성능을 최적화하더라도, 선언이 그대로 유지된다면 호출부 코드는 수정할 필요가 없습니다.

이런 분리 덕분에 각 부분을 독립적으로 컴파일할 수 있고, 이는 유지보수성 향상으로 이어집니다. 다만 헤더 파일이나 모듈을 통해 선언을 여러 파일에 공유하는 구체적인 방법(3.2절)은 이 글의 범위를 벗어나므로 다루지 않습니다.

FAQ

Q. 함수 호출 코드보다 구현 본문 전체가 항상 위에 있어야 합니까?

A. 아닙니다. 호출부 위에는 선언만 있으면 되며, 정의는 파일 아래쪽이나 다른 파일에 있어도 문제없이 동작합니다.

Q. 선언과 인터페이스는 C++에서 같은 의미로 쓰입니까?

A. 네, 3.1장 맥락에서 선언은 함수나 타입을 사용하기 위해 필요한 최소한의 정보인 인터페이스 역할을 합니다.

출처

C++ 학습노트 — 3.1 Introduction (A Tour of C++)