C++

C++ 나쁜(?) 습관, 네임 스페이스 STD

김구티2 2024. 2. 13. 18:48

네임스페이스 std를 사용하는 것은 일반적으로 잘못된 관행으로 간주된다. 그렇기에 이것에 대한 대안은 유형을 선언할 때마다 스코프 연산자(:)를 사용하여 식별자가 속한 네임스페이스를 지정하는 것이다.


이렇게 되면 std::를 입력하지 않아도 되지만, std 네임스페이스에 정의된 클래스나 유형에 액세스할 때마다 std 네임스페이스 전체를 프로그램의 현재 네임스페이스로 가져온다. 그런데 결국 그래서 그것들이 왜 좋고 나쁘다는 것일까? 그것에 대해 학습해 보는 시간을 갖자.

 

예시:

#include <iostream>
using namespace std;
 
cout << " Something to Display";

 

일례로, 이 상황에서 우리는 "foo"라는, 일부 라이브러리에서 사용자 지정으로 구현된 다른 버전의 cout을 사용하고자 한다.

 

#include <foo.h>
#include <iostream>
using namespace std;

cout << " Something to display";

 

그럴 경우, 이제는 cout이 어느 라이브러리를 가리키는지 분명하다고 할 수 있을까? 아니, 매우 모호해졌다. 그렇기에 컴파일러가 이를 탐지하여 프로그램을 컴파일하지 않을 수도 있는 것이다. 최악의 경우, 식별자가 어느 네임스페이스에 속하는지 지정하지 않았기 때문에 프로그램은 여전히 컴파일해도, 잘못된 함수를 호출할 수도 있을 것이다.


식별자 이름 충돌을 해결하기 위해 네임스페이스가 C++에 도입되었다. 이를 통해 두 개체가 서로 다른 네임스페이스에 속할 경우, 동일한 이름을 가지면서도 서로 다르게 취급될 수 있다. 이 예시에서는 그 반대의 경우가 어떻게 발생했는지를 인지해야 한다. 이름의 충돌을 해결하는 것이 아니라, 오히려 이름 충돌을 만들어 내는 상황이 되었으니 말이다.

 

네임스페이스를 가져올 때, 이것은 본질적으로 모든 유형의 정의를 현재 범위로 끌어오는 것이다. 그런데 std 네임스페이스는 매우 크다. 수백 개의 미리 정의된 식별자가 있으므로, 개발자가 std 라이브러리에 자신이 의도한 개체에 대한 다른 정의가 있다는 사실을 간과할 수 있다. 이를 모르는 상태에서 자신의 구현을 지정하고 프로그램의 나중 부분에서 사용될 것으로 예상할 수 있다. 따라서 현재 네임스페이스에는 동일한 유형에 대한 정의가 2개 존재한다. 이는 C++에서 허용되지 않으며, 프로그램이 컴파일하더라도 어떤 정의가 어디에 사용되는지 알 수 있는 방법이 없게 된다. 그렇기에 문제가 되는 것이고, 그렇기에 나쁜 습관이라고 말하는 것이다.

이런 문제에 대한 해결책은 스코프 연산자(::)를 사용하여 식별자가 어느 네임스페이스에 속하는지 명시적으로 지정하는 것이다. 따라서 위 예시에 대한 한 가지 가능한 해결책은 아래와 같다.

 

#include <foo>
#include <iostream>

// Use cout of std library
std::cout << "Something to display";

// Use cout of foo library
foo::cout < "Something to display";

 

하지만 std::를 입력해야 하는 것은 유형을 정의할 때마다 분명 번거로운 일이다. 또한, 많은 유형 정의로 인해 코드를 읽는 게 더 복잡해질 수도 있는 노릇이다. 다음의 예시를 보고 이를 이해해 보자.

 

#include <chrono>
#include <iostream>

auto start = std::chrono::high_performance_clock::now()

// Do Something

auto stop
     = std::chrono::high_peformance_clock::now();
auto duration 
     = std::duration_cast<std::chrono::milliseconds>(stop - start);

 

복잡하고 긴 유형의 정의가 흩어져 있는 소스 코드는 읽기가 그리 쉽지 않다. 물론 나도 전문가가 아니기에 그런 코드를 보면 눈이 피로해진다. 생각해 보니 전문가분들도 마찬가지긴 하겠지만 말이다. 코드 유지 보수성은 개발자들에게 가장 중요하기 때문에 이런 복잡성은 개발자들에게는 분명 피해야 하는 요소일 것이다. 코드는 간결할수록 좋으니 말이다.


그리고 std 키워드로 코드를 버리지 않고 정확한 네임스페이스를 지정하는 방법이 있다.

type defs를 사용하는 것이 그 해결책이다.
type defs는 긴 유형의 정의를 쓰는 것을 막아준다. 위 예시 중 std 라이브러리와 foo 라이브러리가 쓰인 것에 대해 type defs를 사용하면 문제가 해결된다.

 

#include <foo>
#include <iostream>

typedef std::cout cout_std;
typedef foo::cout cout_foo;

cout_std << "Something to write";
cout_foo << "Something to write";

 

 

전체 네임스페이스를 가져오는 대신, 잘린 네임스페이스를 가져오는 것이다. 다른 예시를 한번 더 보자.

 

#include <chrono>
#include <iostream>

// Import only the chrono namespace under std
using std::chrono;

auto start = high_performance_clock::now();

// Do Something
auto stop = high_performance_clock::now();
auto duration duration_cast<milliseconds>(stop - start);

 

단일 식별자를 가져올 때도 이렇게 사용할 수 있다.

 

그럼에도 여전히 전체 네임스페이스를 가져오는 경우, 전역 범위가 아닌 함수 또는 제한된 범위 내에서 해보는 것을 고려하는 것도 방안이다.
함수 정의 또는 클래스, 구조 정의 안에서 네임스페이스 std 문을 사용하는 것이다. 이렇게 하면 네임스페이스 정의를 로컬 범위로 가져올 수 있으며, 오류가 발생할 경우에는 적어도 발생할 수 있는 위치를 알 수는 있다. 이것만 해도 큰 선방이지 않겠는가. 물론 오류가 없는 게 베스트이긴 하지만 말이다.

 

#include <iostream>

// Avoid this
using namespace std;

void foo()
{
     // Inside function
     // Use the import statement inside limited scope
     using namespace std;

     // Proceed with function
}

 

여기까지 네임스페이스에서 식별자에 액세스하는 대체 방법에 대해 논의했다. 이제 전체 네임스페이스를 소스 코드로 가져오는 것을 피해야 하는 이유에 대해 알 수 있을 것이다.

 

내가 글을 쓸 때 나만의 습관이 분명 존재할 것이다. 그리고 코딩도 마찬가지이다. 개발자들의 코딩에는 자신만의 습관이 있기 마련이다. 그렇기에 C++를 최초에 공부하는 사람일수록 처음부터 습관을 잘 들여놓는 것이 좋을 것이다. 그것이 분명 장기적인 관점에서 본인에게 이득이 될 테니 말이다.

 

코드 작성에서 가장 중요한 것은 모호성이 없는 명료하고도 간결한 코드이다. 이것을 잊지 말자.

 

728x90

'C++' 카테고리의 다른 글

C++ 식별자(Identifiers) 총정리  (0) 2024.02.15
C++ 기본 구문(Syntax) 총정리  (0) 2024.02.15
C++ 전처리기(#) 총정리  (1) 2024.02.12
C++ 주석 총정리  (1) 2024.02.11
C++ 프로그램 작성의 시작, Hello World 학습  (1) 2024.02.10