Programming/C, C++

[C++] namespace 사용법 읽을거리

변화의 물결1 2025. 4. 23. 00:04

 

 

안녕하세요.

 

 C++ 프로그래밍에서 코드의 규모가 커지거나 여러 라이브러리를 사용할 때 발생할 수 있는 이름 충돌(Name Collision) 문제이 발생하기도 하고, 코드를 논리적으로 구분할 때 namespace를 사용할 텐데요. 조금 알아보겠습니다.

 


 

1. namespace란 무엇이고 왜 사용할까?

 

 C++ 프로젝트를 진행하다 보면, 내가 만든 함수나 변수 이름이 다른 라이브러리나 팀원이 만든 코드의 이름과 겹치는 경우가 생길 수 있습니다.

 예를 들어, print()라는 함수를 여러 곳에서 정의한다면 컴파일러는 어떤 함수를 호출해야 할지 알지 못해 오류를 발생합니다.

 

 namespace는 이러한 이름 충돌 문제를 해결하기 위해 도입된 문법입니다. 특정 코드 영역을 고유한 이름 공간(namespace)으로 묶어, 그 안에 선언된 이름(변수, 함수, 클래스 등)들이 다른 영역의 이름과 격리되도록 합니다.

 마치 폴더를 만들어 파일을 정리하듯, 관련 있는 코드 요소들을 하나의 namespace 안에 그룹화하여 코드의 구조를 명확하게 하고 유지보수를 용이하게 합니다.

 

 

2. namespace 기본 문법

 

 namespace는 namespace 키워드와 이름, 그리고 중괄호 {}로 정의합니다. 대괄호 끝에 ;세미콜론(semicolon) 붙이지 않아도 됩니다.

 

namespace BasicSpace { // 'BasicSpace' 라는 이름의 네임스페이스 정의
    // 이 안에 선언된 이름들은 BasicSpace 소속입니다.
    int version = 1;

    void printInfo() {
        std::cout << "BasicSpace version: " << version << std::endl;
    }

    class Widget {
        // ...
    };
}

 

 

3. 범위 확인 연산자( :: ) - namespace 멤버에 접근하는 방법

 

 namespace 안에 선언된 멤버에 접근하려면 범위 확인 연산자 (Scope Resolution Operator)인 :: 를 사용해야 합니다. 사용법은 네임스페이스이름::멤버이름 형식입니다.

 

 

#include <iostream> // 표준 라이브러리 사용을 위해 포함

namespace Graphics {
    void initialize() { std::cout << "Graphics Initialized\n"; }
    int screenWidth = 1920;
}

namespace Audio {
    void initialize() { std::cout << "Audio Initialized\n"; }
    double volume = 0.8;
}

int main() {
    // 각 네임스페이스의 멤버에 :: 를 사용하여 접근
    Graphics::initialize();
    Audio::initialize();

    std::cout << "Screen Width: " << Graphics::screenWidth << std::endl;
    std::cout << "Volume: " << Audio::volume << std::endl;

    // 만약 그냥 initialize(); 라고 호출하면 어떤 것을 호출할지 모호해 컴파일 오류 발생
    // initialize(); // Error: 'initialize' is ambiguous
    return 0;
}

 

 

4. 전역 네임스페이스 (Global Namespace)

 

  특별한 namespace 안에 속하지 않고 코드 파일의 최상위 레벨에 선언된 이름들은 전역 네임스페이스에 속합니다.

 전역 네임스페이스의 멤버에 명시적으로 접근하고 싶을 때는 이름 앞에 :: 를 붙입니다 (::전역멤버이름).

 이는 지역 변수나 다른 네임스페이스의 멤버와 이름이 같을 때 전역 멤버임을 명확히 하기 위해 사용될 수 있습니다.

 

#include <iostream>

int count = 100; // 전역 네임스페이스 변수

namespace Counter {
    int count = 0; // Counter 네임스페이스 변수
}

int main() {
    int count = 1; // main 함수의 지역 변수

    std::cout << "Local count: " << count << std::endl;           // 출력: 1 (지역 변수)
    std::cout << "Counter::count: " << Counter::count << std::endl; // 출력: 0 (Counter 네임스페이스 변수)
    std::cout << "Global count: " << ::count << std::endl;         // 출력: 100 (전역 네임스페이스 변수)
    
    return 0;
}

 

 

5. 네임스페이스 확장 (Namespace Extension)

 

 동일한 이름의 namespace를 여러 번 선언하여 기존 namespace에 새로운 멤버를 추가하는 것이 가능합니다.

 이를 네임스페이스 확장이라고 하며, 코드를 여러 파일에 분산하여 관리할 때 유용합니다.

 

// --- file1.h ---
namespace CoreSystem {
    void coreFunction();
    int coreValue;
}

// --- file2.h ---
namespace CoreSystem { // CoreSystem 네임스페이스 확장
    void utilityFunction();
    // int coreValue = 10; // 오류! 이미 선언된 멤버를 다시 정의할 수는 없음
    int utilityValue;
}

// --- main.cpp ---
#include "file1.h"
#include "file2.h"
#include <iostream>

// 구현은 .cpp 파일에서...
namespace CoreSystem {
    void coreFunction() { std::cout << "Core function\n"; }
    int coreValue = 5;
    void utilityFunction() { std::cout << "Utility function\n"; }
    int utilityValue = 10;
}

int main() {
    CoreSystem::coreFunction();
    CoreSystem::utilityFunction();
    std::cout << "Core Value: " << CoreSystem::coreValue << std::endl;
    std::cout << "Utility Value: " << CoreSystem::utilityValue << std::endl;

    return 0;
}

 

 주의할 점은, 네임스페이스를 확장할 때 기존에 있던 멤버와 동일한 이름으로 새로운 멤버를 정의하려고 하면 컴파일 오류가 발생합니다. 확장은 새로운 멤버를 추가하는 기능입니다.

 

 

6. C++과 Partial Class

 

 C#에서는 partial 키워드를 사용하여 하나의 클래스 정의를 물리적으로 여러 파일에 나누어 작성하는 기능을 제공합니다. 이를 Partial Class (부분 클래스)라고 합니다.

 

 

// file1.cs (C# 예시)
public partial class MyClass {
    public void Method1() { /* ... */ }
}

// file2.cs (C# 예시)
public partial class MyClass { // 같은 클래스를 다른 파일에서 이어 작성
    public void Method2() { /* ... */ }
}

 

 C++ 에는 C#과 같은 partial 키워드나 동일한 방식의 Partial Class 기능이 없습니다. 즉, 하나의 클래스 정의(멤버 변수 선언, 멤버 함수 선언 등)는 일반적으로 하나의 헤더 파일(.h, .hpp) 내에서 완료되어야 합니다.

 

하지만 C++에서도 코드를 여러 파일로 분리하여 관리하는 일반적인 방법이 있습니다.

 

 선언과 정의 분리: 클래스의 선언 (멤버 변수, 함수 시그니처 등)은 헤더 파일(.h, .hpp)에 작성하고, 멤버 함수의 실제 정의(구현)는 별도의 소스 파일(.cpp)에 작성합니다. 이는 Partial Class와는 다른 개념이지만, 코드 분리를 통해 관리하여 편의성을 높입니다.

 

 네임스페이스 확장 활용: 위에서 설명한 네임스페이스 확장을 사용하여 관련된 함수, 클래스, 변수 등을 논리적으로 같은 namespace 안에 묶으면서 물리적으로 다른 파일에 분산시킬 수 있습니다.

 

 결론적으로 C++은 partial 키워드를 지원하지 않으며, 코드 분리는 주로 헤더/소스 파일 분리와 네임스페이스 확장을 통해 이루어집니다.

 

 

7. 다양한 범위의 이름 접근 실습

 

 아래 코드는 전역 네임스페이스, 사용자 정의 네임스페이스(space), 그리고 네임스페이스 내의 클래스(BASE_SPACE)에 동일한 이름(a1)의 변수가 어떻게 존재하고 접근되는지 보여줍니다.

 

 

#include <iostream>

int a1 = 1; // 전역 네임스페이스의 a1

namespace space {
    int a1 = 2; // 'space' 네임스페이스의 a1

    class BASE_SPACE {
    public :
        static const int a1 = 3; // BASE_SPACE 클래스의 정적 상수 멤버 a1
        static int getNumber() {
            return a1; // 클래스 내부이므로 BASE_SPACE::a1 (3)을 반환
        }

        void print() {
            // 다양한 범위의 a1에 접근
            std::cout << "print - Global ::a1 : " << ::a1 << std::endl;       // 전역 a1 (1)
            std::cout << "print - space::a1  : " << space::a1 << std::endl;    // space 네임스페이스 a1 (2)
            std::cout << "print - Class a1   : " << a1 << std::endl;          // 클래스 멤버 a1 (3)
        }
    };
}

namespace space { // 네임스페이스 확장
    // int a1 = 10; // 에러: 'space'에 a1은 이미 정의되어 있음
    int b1 = 20;    // 새로운 멤버 b1 추가
}

int main()
{
    space::BASE_SPACE base; // space 안의 BASE_SPACE 객체 생성
    base.print();           // 객체의 print 함수 호출

    std::cout << "main - Global a1 (default): " << a1 << std::endl;       // 전역 a1 (1)
    std::cout << "main - Global ::a1        : " << ::a1 << std::endl;      // 명시적 전역 a1 (1)
    std::cout << "main - space::a1         : " << space::a1 << std::endl;   // space 네임스페이스 a1 (2)
    std::cout << "main - Class static a1   : " << space::BASE_SPACE::getNumber() << std::endl; // 클래스 정적 멤버 a1 (3)
    std::cout << "main - space::b1         : " << space::b1 << std::endl;   // 확장된 space 네임스페이스 멤버 b1 (20)
}

 

<소스코드 실행 결과>

 

 

 

8. 실습 내용을 다이어그램으로 (Mermaid.js)

 

 아래 다이어그램은 실습 코드의 이름 범위(전역, space 네임스페이스, BASE_SPACE 클래스)와 각 범위에서 :: 연산자를 통해 멤버에 접근하는 경로로 표현해 보았습니다.

 

 조금 복잡해 보이는데 호출한 함수와 변수를 연결해 놓은 것으로 생각하면 될 듯합니다.

 

 

 

감사합니다.

 

 

반응형