반응형

C++ 코드를 보다보니 [[ ~ ]] 와 같은 형식으로 작성된 부분 있었다.

 

뭔가 싶어서 검색해보니 컴파일러에게 코드에 대해서 좀 더 명확하게 힌트를 제공하는 기능이라고 한다.

 

그중에서 자주 사용할것 같은 attribute만 간단하게 정리해보고자 한다. (개인적인 기준;;)

 

1. [[nodiscard]] - C++17

 

이 attribute가 붙은 함수나 attibute가 붙은 enum, class등을 리턴하는 함수를 만들었다면

해당 함수를 호출할때는 리턴값을 받아야 한다고 컴파일러에 알려주는 것이다.

이는 컴파일 시점에 워닝으로 알려준다.

 

[[nodiscard]] int strategic_value(int x, int y) { return x^y; }

int main()
{
  strategic_value(); // warning
  auto v = strategic_value(); // ok

  return 0;
}

 

[[nodiscard("string"]] - C++ 20은 워닝 메시지에 "string" 부분도 같이 남겨준다.

 

2. [[deprecated]] / [[deprecated("string") - C++14

 

해당 attribute가 붙은 함수를 사용하면 사용은 가능하지만 앞으로 제거될 수 있음을 워닝으로 알려준다.

엔진이나 라이브러리를 만들때 많이 사용될 것 같다.

언리얼 엔진에서도 버전업 하고 빌드해보면 나올때가 있다. 그러면 최신에 맞게 코드를 수정해주곤 한다.

 

[[deprecated]]
void TriassicPeriod() {
    std::clog << "Triassic Period: [251.9 - 208.5] million years ago.\n";
}
 
int main()
{
    TriassicPeriod(); // warning
    return 0;
}

 

3. [[fallthrough]] - C++17

 

switch / case 문을 사용하다보면 fallthrough 기능을 사용하곤한다.

컴파일러에 따라서 해당 부분을 워닝처리할때가 있는데 컴파일러에 명확하게

내가 fallthrough를 한거니까 워닝처리를 하지 말라고 하는 것이다.

 

void f(int n) {
  void g(), h(), i();
  switch (n) {
    case 1:
    case 2:
      g();
     [[fallthrough]];
    case 3: // no warning on fallthrough
    ..
  }
}

몇가지 더 있기도 하고 추가될 예정인것 같지만 일단 이정도만 알고 사용해도 되지 않을까 싶다.

반응형
Posted by msparkms
,
반응형

C++11에 유니폼 초기화가 추가되었다.

{}를 이용한 초기화여서 Brace-Initialization 이라고도 부르는것 같다.

 

C++11에 {}를 이용해서 초기화 문법을 따로 추가한 이유는 무엇일까??

사실 이전부터 일부 상황에서는 {}를 이용한 초기화를 사용하고 있었고

일부 상황에서는 ()를 이용한 초기화를 사용하고 있었다.

 

구조체와 배열의 경우에는 {}를 이용해서 초기화를 하고 있었고

하지만 클래스는 ()를 이용해서 생성자를 호출해서 초기화 하는식으로 처리가 되고 있었다.

struct A
{
  int x, y;
};

class B
{
  public:
    B(int x, int y) : mX(x), mY(y) {}
  private:
    int mX, mY;
};


A a = {10, 20};			// {}
B b(10, 20);			// ()

int c[4] = {1, 2, 3, 4};	// {}

C++11에서는 이 부분을 모두 {}를 이용하는 유니폼 초기화로 문법을 통일시켰다고 보면 될 것 같다.

=을 붙이거나 붙이지 않거나 모두 유니폼 초기화를 사용할 수 있다.

A a1 = {10, 20};
B b1 = {10, 20};

A a2{10, 20};
B b2{10, 20};

유니폼 초기화는 일반 자료형에도 사용 가능하다. 아래 예제에서 {}로 초기화할 경우 0이 들어간다.

int a = 1;
int b(1);

// uniform-initialization
int c = {1};	
int d{1};
int e{};	// 0

기왕 유니폼 초기화 기능을 넣고 문법을 통일하면서 한가지 기능을 더 추가하였는데

바로 축소 변환 방지(narrowing) 기능이다.

이는 암묵적 캐스팅이 진행될 때 기존 데이터의 손실이 발생할 경우 에러를 처리해주는 기능을 말한다.

예를 들자면 int에 double 값을 넣을 경우에 소수부분은 손실되게 되는데 이러한 상황을 막는 것이다.

void PrintInt(int x)
{
  std::cout << "Num : " << x << std::endl;
}

A i { 3.14, 3.14 };	// warning
B j { 3.14, 3.14 };	// error
int k { 3.14 };		// error

PrintInt(3.14);		// warning
PrintInt({ 3.14 });	// error

내가 테스트한 환경에서는 구조체의 경우에는 warning 처리로 끝나기는 했고 나머지의 경우에는 모두 축소 변환 에러가 발생했다. (VS 2019 - C++17)

 

클래스 멤버 변수를 선언하면서 바로 초기화 하거나 초기화 리스트에서 배열을 초기화 할때도 사용할 수 있다.


다음은 직접 리스트 초기화와 복제 리스트 초기화에 대한 부분을 간단히 정리하고 넘어가려고 한다.

위에 예제를 보면 a = {}의 형식이거나 아니면 a {} 형식으로 초기화를 하였다.

 

a = {}와 같은 형태를 복제 리스트 초기화라고 하고

a {}와 같은 형태를 직접 리스트 초기화라고 한다.

 

그냥 생성자 호출과 복사 생성자 호출 (= 연산자) 의 느낌이라고 대충 생각해도 될 것 같다.

 

여기서 이야기할 내용은 {}를 auto로 받았을 경우에 타입이 무엇이냐에 대한 것이다.

{}를 auto로 받으면 initializer_list<>으로 변경되고 처리가 되는데 C++17 이전과 이후에 약간 변화가 있어서 정리하려고 한다.

 

C++17 이전은 모두 initializer_list<>로 처리가 되고 C++17은 복제 리스트 초기화만 initializer_list<>로 처리된다.

// before C++ 17
auto a1 = { 1 };	// initializer_list<int>
auto a2 = { 1, 2 };	// initializer_list<int>
auto a3{ 1 };		// initializer_list<int>
auto a4{ 1, 2 };	// initializer_list<int>
auto a5 = { 1, 1.1 };	// error

// after C++ 17
auto a1 = { 1 };	// initializer_list<int>
auto a2 = { 1, 2 };	// initializer_list<int>
auto a3{ 1 };		// int
auto a4{ 1, 2 };	// error
auto a5 = { 1, 1.1 };	// error

C++17 이후 a4의 경우에는 초기화 리스트가 아닌 타입을 추론하게 되는데

이때 하나의 값에 의한 추론만 진행하기 때문에 값이 여러개라고 에러가 발생하게 된다.

그리고 a5의 경우에는 복제 초기화 리스트라도 다른 타입의 값을 넘긴다면 추론에 실패한다.

 

참고로 내가 테스트한 환경에서는 C++14로 낮춰도 a4 부분이 에러가 발생하기는 했다. (VS2019 - C++14)

 

마지막으로 어떤 타입인지 직접 출력해보고 싶으신 분들은

typeinfo의 typeid를 이용해서 콘솔로 출력해보면 좋을 것 같다.

반응형
Posted by msparkms
,
반응형

C++ 17에 std::string_view가 추가되었다.

기존에 std::string이 있는데 std::string_view가 따로 추가된 이유는 무엇일까?

 

문자열을 다루는 함수를 만든다고 해보면 매개변수를

const char* 혹은 const std::string& 으로 만드는 것이 보통이다.

이 두가지 선택은 각각 장단점을 가지게 된다.

 

const char*를 선택했다면 문자열 리터럴을 포함한 문자열 포인터를 받는데 용이하다.

대신 std::string에서 제공해주는 많은 기능을 사용할 수 없다.

 

const std::string&을 선택했다면 std::string의 기능을 사용할 수 있지만

문자열 리터럴을 받는다면 std::string 객체가 생성된다.

 

함수를 오버로딩해서 const char*, const std::string& 함수를 모두 만들면 해결은 되지만

뭔가 좋아보이지는 않는다.

 

두가지 장점을 모두 가져가기 위해 추가된 것이 std::string_view이다.

std::string_view는 내부에 포인터와 길이정보를 가진다.

그래서 문자열 포인터를 바로 받을 수 있으면서 std::string의 기능을 제공해준다.

포인터와 길이 정보만 가지고 있기 때문에 보통 레퍼런스가 아닌 값을 전달하는 방식으로 구현한다.

using namespace std::string_view_literals;

void StringViewTest(std::string_view strv)
{
    std::cout << "str : " << strv.data() << " length : " << strv.length() << std::endl;
}

StringViewTest("abcdefg");

std::string TestString("Test");
StringViewTest(TestString);

auto sv = "My Test String View"sv;
StringViewTest(sv);

간단한 예제는 위와 같다.

std::string_view를 매개변수로 받는 함수로 문자열 리터럴, std::string, std::string_view를 처리할 수 있다.

 

참고로 using namespace std::string_view_literals;를 추가하면

문자열 뒤에 sv를 붙여줌으로써 string_view 리터럴을 사용할 수 있다.

반응형
Posted by msparkms
,