최근 수정 시각 : 2024-11-03 15:55:39

C++/문법/이름공간

파일:관련 문서 아이콘.svg   관련 문서: C++
, 이름공간
,
,
,
,

파일:상위 문서 아이콘.svg   상위 문서: C++/문법
프로그래밍 언어 문법
{{{#!wiki style="margin: -16px -11px; word-break: keep-all"<colbgcolor=#0095c7><colcolor=#fff,#000> 언어 문법 C(포인터 · 구조체 · size_t) · C++(자료형 · 클래스 · 이름공간 · 상수 표현식 · 특성) · C# · Java · Python(함수 · 모듈) · Kotlin · MATLAB · SQL · PHP · JavaScript · Haskell(모나드)
마크업 문법 HTML · CSS
개념과 용어 함수(인라인 함수 · 고차 함수 · 람다식) · 리터럴 · 상속 · 예외 · 조건문 · 참조에 의한 호출 · eval
기타 == · === · deprecated · NaN · null · undefined · 배커스-나우르 표기법
프로그래밍 언어 예제 · 목록 · 분류 }}}

1. 개요2. Global Namespace3. using
3.1. 이름공간 멤버 차용3.2. 이름공간 차용
4. 중첩 이름공간5. inline6. 이름공간 별칭7. 익명 이름공간

1. 개요

namespace 식별자
{
...
}
이름 공간 (Namespace)
이름공간, 또는 네임스페이스는 식별자 사이의 이름 충돌을 막기 위한 장치이다. 이름공간은 각각 분리된 프로그램처럼 존재하며 다른 이름공간끼리는 별도의 지시자없이는 참조할 수 없다 [1].

C언어에서 가장 큰 문제 중 하나가 프로젝트가 커질수록 식별자의 명칭이 겹칠 위험이 커지는 것이였다. 프로젝트가 일정 규모 이상 커지거나, 외부 라이브러리를 사용하는 경우 객체[2]들의 이름 중복 현상이 발생할 가능성이 커진다. 예를 들어 비슷한 용도의 반복적인 기저 코드 작성(Boilerplate)이 프로젝트에서 반복되다 보면 단어란 단어는 다 소모하고 비슷한 유틸리티 함수가 늘어난다. 이때 다른 라이브러리 사이에 같은 이름의 객체가 있으면 컴파일러는 이를 구분할 수 없다. 차라리 모호하다고 컴파일 오류를 내거나 런타임 오류라도 나면 다행이지만, 그렇지 않고 정상작동하는 것처럼 보이는 코드가 되면 예측할 수 없는 동작을 할 것이다. C++에서 도입된 이름 공간은 큰 규모의 프로그램 개발에서 객체의 명명 문제를 대부분 해결해준다 [3].

<C++ 예제 보기>
#!syntax cpp
import <string>;
import <print>;

namespace Namu
{
    class MyPrinter
    {
    public:
        void Execute() const
        {
            std::println(myCaption);
        }

    private:
        std::string myCaption = "나무위키";
    };
}

namespace Wiki
{
    class MyPrinter
    {
    public:
        void Execute() const
        {
            std::println(myCaption);
        }

    private:
        std::string myCaption = "NamuWiki";
    };
}

int main()
{
    Namu::MyPrinter printer1;
    Wiki::MyPrinter printer2;

    printer1.Execute();
    printer2.Execute();

    return 0;
}
예제 코드의 콘솔에서의 실행 결과는 다음과 같다.
나무위키
Namu Wiki
첫번째 줄에는 '나무위키'가 출력되고 두번째 줄에는 'NamuWiki'가 출력된다.

2. Global Namespace

<C++ 예제 보기> [ 펼치기 / 접기 ]
#!syntax cpp 
import <print>;
import <string>;

class Namu
{
public:
    void Execute() const
    {
        std::println(myCaption);
    }

private:
    std::string myCaption = "Namu";
};

class Wiki
{
public:
    void Execute() const
    {
        std::println(myCaption);
    }

private:
    std::string myCaption = "Wiki";
};

int main(void)
{
    Namu printer1;
    Wiki printer2; // 또는 ::Wiki printer2;

    printer1.Execute();
    printer2.Execute();

    return 0;
}
전역적 이름공간 (Global Namespace)
이름공간이 명시 되어있지 않을 때는 전역적 이름공간을 사용하게 된다. C언어도 기본적으로 전역적 이름공간을 사용한다. 이름 공간의 이름을 적지 않고 ::만을 사용하면 전역 이름공간을 사용하겠다고 지시할 수 있다.

그러나 전역 이름공간의 사용은 일반적으로 권장되지 않는다. 이름공간의 목적은 다른 저작자의 코드를 구분하거나, 다른 역할을 하지만 혹여나 중복될 수 있는 이름을 구별하기 위해 만들어 진 것인데 전역 이름공간은 이를 무력화시킨다. 또한 전통적인 헤더 파일 구조에서 전역 이름공간을 사용하면 그 헤더를 삽입하는 소스 코드도 영향을 받기 때문이다. 만약 C++20의 모듈이라도 export한 객체가 전역적 이름공간에 있다면 피할 수 없는 문제다. 그렇기에 소스 구현부 또는 스코프 안에서만 사용하는 것이 좋다.

3. using

<C++ 예제 보기>
#!syntax cpp
namespace NamuWiki
{
    struct Squirrel { float x, y, z; };

    Squirrel MakeSquirrel() noexcept
    {
        return Squirrel{};
    }

    Squirrel MakeSquirrel(float x, float y, float z) noexcept
    {
        return Squirrel{ x, y, z };
    }
}

int main()
{
    // (1)
    // 일반적인 경우 이름공간을 명시해야 한다
    NamuWiki::Squirrel squirrel0{};

    // auto를 통해 이름공간을 일일히 기입하는 수고를 덜 수 있다
    auto squirrel1 = NamuWiki::MakeSquirrel(30, 20, 40);

    // (2)
    using NamuWiki::Squirrel;

    // 이제 NamuWiki::Squirrel을 그대로 사용할 수 있다
    const Squirrel squirrel3;

    // (3)
    using NamuWiki::MakeSquirrel;

    // 이제 함수 NamuWiki::MakeSquirrel의 모든 오버로딩을 그대로 사용할 수 있다
    NamuWiki::Squirrel squirrel4 = MakeSquirrel();
    NamuWiki::Squirrel squirrel5 = MakeSquirrel(50, 600, 130);

    // (4)
    // 이제 이름공간 NamuWiki 안의 멤버를 모두 그대로 사용할 수 있다.
    using namespace NamuWiki;

    Squirrel squirrel6 = MakeSquirrel(40, 100, 200);
}

3.1. 이름공간 멤버 차용

using Namespace::Function;
using Namespace::Variable;
using typename Namespace::Type;
using Namespace::Type;
using 선언문 (Using Declarations)
using을 이용하면 이름공간의 멤버에 접근할 때 축약된 표현을 사용할 수 있다. 이 기능은 이름공간을 이용하면서 이름공간의 식별자를 일일이 표기하면서 생기는 불편함을 줄여준다. 주목할 점은 선언문이라는 것인데, 이름공간의 같은 멤버에 계속 using 선언문을 써도 문제가 없다. 명시한 이름공간의 멤버를 현재 이름공간으로 끌어오는 논리라 중복 정의의 문제가 없다.

예를 들어 using [이름공간 식별자]::[멤버 이름];이라고 적은 경우를 생각해보자. using [이름공간 Ns]::[클래스 Type];가 선언된 경우에는 클래스 Type에 대한 인스턴스를 선언할 때에 Ns::Type [인스턴스 이름];이라고 적지 않고 Type [인스턴스 이름]이라고 적을 수 있다. using [이름공간 Ns]::[함수 Function];이 선언된 경우에는 해당 함수를 호출할 때에 Ns::Function(...);이 아니라 바로 Function(...);으로 호출할 수 있다.

3.2. 이름공간 차용

namespace Identifier1
{
...
}
namespace Identifier2
{
using namespace Namespace;
}
using namespace Namespace;
using 지시자 (Using Directive)
using 지시자 기능을 사용하면 이름공간 안의 모든 멤버를 현재 이름공간으로 가져올 수 있다. 이러면 이름공간의 식별자를 명시하지 않고도 그 이름공간 안에 있는 모든 멤버에 바로 접근할 수 있다. C++에서 가장 많이 사용되는 using namespace std; 구문은 C++의 표준 라이브러리에 대한 이름공간인 std를 명시하지 않고 항상 std안의 멤버를 사용하겠다는 뜻이다. 예를 들어 std::cout 대신에 cout라고 축약해서 명시할 수 있다.

예를 들어 using namespace [네임스페이스 Ns];가 선언된 경우에는 이미 using [네임스페이스 Ns]::[클래스 Type];using [네임스페이스 Ns]::[함수 Function];를 선언한 것과 같아서 Ns::Type [인스턴스 이름]; 대신에 바로 Type [인스턴스 이름]이라고 인스턴스를 생성할 수 있고, Ns::Function(...);이 아니라 Function(...);를 바로 호출할 수 있다.

그런데 헤더 파일의 전역 이름공간에는 using을 사용하지 않는 것이 좋다. 이는 C++#include문이 헤더 파일의 내용을 그대로 복사 & 붙여넣기를 하는 식으로 작동하기 때문이다. using 문을 사용하는 헤더를 포함하면 이를 삽입한 모든 파일에 using 문이 강제적으로 적용되어 버리고, using을 취소할 방법이 있는 것도 아니기에 남의 코드를 직접 수정해야 하는 영 좋지 않은 상황이 만들어진다.

4. 중첩 이름공간

namespace Identifier1::Identifier2
{
...
}
namespace Identifier3::Identifier4::Identifier5::IdentifierN...
{
...
}
using namespace Identifier1::Identifier2;
using Identifier3::Identifier4::멤버;
중첩 이름공간 (Nested Namespace)C++17
C++17부터 이름공간 선언을 ::와 중첩해서 할 수 있게 되었다. C++ 입문 단계를 넘어가면 편의성을 체감할 수 있는 기능이다. C++17 이전에는 이름공간 쓰기가 매우 불편했는데 간단한 문법 개선으로 편의성을 증대시켰다.

5. inline

inline namespace Identifier1
{
// C++11
...
}
namespace Identifier2
{
inline namespace Identifier3
{
// C++11
...
}
}
namespace Identifier4::inline Identifier5
{
// C++20
...
}
inline namespace Identifier6::{{{#DodgerBlue,#CornFlowerBlue 'inline}}} Identifier7::{{{#DodgerBlue,#CornFlowerBlue inline'}}} IdentifierN...
{
// C++20
...
}
인라인 이름공간 (Inline Namespace)C++11 , 중첩된 인라인 이름공간 (Nested Inline Namespace)C++20
인라인 이름공간을 사용하면 선언한 이름공간의 내용물을 상위 이름공간에서 바로 사용할 수 있다. 사실 인라인 이름공간의 원리는 상위 이름공간에 내용을 먼저 집어넣고, 인라인 이름공간 안에서 using을 쓰는 것에 가깝다. 인라인 이름공간을 쓰면 이전에 선언된 이름공간의 식별자를 덮어쓴다. 이를 이용해 언어 내부에서 버전 관리를 할 수 있다. MicrosoftDirectXWinUI에서 이런 식으로 기능을 업그레이드하고 있다.

<C++ 예제 보기>
#!syntax cpp 
// (1)
// 중첩 이름공간 선언
// 해당 클래스 선언들은 이름공간 NamuWiki::Documents::ProgrammingLanguage에도 공존하게 된다
namespace NamuWiki::Documents::ProgrammingLanguage::inline FromEnhaWiki
{
    class Assemybly;
    class C;
    class Erlang;
    class Cpp;
    class Java
    class Python;
    class JavaScript;
    class Csharp;
    class Go;
    class Swift;
    class Rust;
}

// (2)
// 식별자 덮어씌우기
namespace House::LoungeRoom::Furnitures
{
    inline namespace Y2019
    {
        class Television
        {
        public:
            std::string hwVender = "Samsung";
        };
        class Fan
        {
        public:
            std::string hwVender = "Dyson";
        };
        class Couch;
        class Chair;
        class LargeTable;
    }

    inline namespace Y2021
    {
        class Television
        {
        public:
            std::string hwVender = "Samsung";
        };
        class Fan // Y2019::Fan 클래스를 덮어쓴다.
        {
        public:
            std::string hwVender = "LG";
        };
        class AirConditioner;
        class MiniBar;
    }

    inline namespace Y2023
    {
        class Television // Y2021::Television 클래스를 덮어쓴다.
        {
        public:
            std::string hwVender = "LG";
        };
    }
}

6. 이름공간 별칭

namespace Identifier1
{
...
}
namespace Identifier2::Identifier3
{
...
}
namespace Identifier4::inline Identifier5
{
...
}
namespace Identifier6
{
namespace NamespaceAlias2 = Identifier2;
namespace NamespaceAlias4 = ::Identifier4;
}
namespace NamespaceAlias1 = Identifier1;
namespace NamespaceAlias3 = Identifier2::Identifier3;
namespace NamespaceAlias5 = Identifier4::Identifier5;
이름공간 별칭 (Namespace Aliases)

7. 익명 이름공간

namespace
{
...
}
이름없는 이름공간 (Unnamed Namespace)C++17
이름공간을 선언할 때 식별자를 지정하지 않으면 이름없는 이름공간 또는 익명 이름공간이 된다. 이 경우 익명 이름공간이 선언된 범위에서 using namespace (익명 이름공간);을 사용하는 것과 같은 동작을 수행한다. 이 기능의 진정한 의의는 static을 대체제로써 내부 연결을 C++의 모든 객체에 적용하는데에 있다. 자세한 내용은 언어 연결성 단락을 참고하자.

<C++ 예제 보기>
#!syntax cpp
namespace CPUVenders
{
    class Intel;
    class AMD;
    class [[deprecated]] VIA;
}

namespace
{
    class Nvidia;
    // CPUVenders::AMD, CPUVenders::Intel과 ::AMD, ::Intel은 다른 존재다.
    // 같은 객체로 취급하려면 using CPUVenders::...를 사용해야만 한다.
    class AMD;
    class Intel;
}

namespace HardwareVenders
{
    namespace
    {
        GigaByte,
        Asus,
    }
    // using (익명)::GigaByte;
    // using (익명)::Asus;

    namespace CPUVenders = ::CPUVenders;

    namespace GPUVenders
    {
        using ::Nvidia;
        using ::AMD;
        using ::Intel;
    }
}



[1] 이를 코드 범위(Scope)가 다르다고 한다[2] 함수, 상수, 클래스[3] 많은 객체 지향 언어가 그렇듯 C++에서도 두 클래스의 이름이 같더라도 속한 이름공간이 다르면 공존할 수 있다

파일:CC-white.svg 이 문서의 내용 중 전체 또는 일부는 문서의 r374에서 가져왔습니다. 이전 역사 보러 가기
파일:CC-white.svg 이 문서의 내용 중 전체 또는 일부는 다른 문서에서 가져왔습니다.
[ 펼치기 · 접기 ]
문서의 r374 (이전 역사)
문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)

문서의 r (이전 역사)