반응형
안녕하세요. 오늘은 잠시 제 본업으로 돌아왔습니다. 업무에 사용중이던 Visual Studio 버전을 2019에서 2022로 올리게 되었고, 동시에 사용하던 C++ 표준이 14에서 20으로 올라가게 되며 그간 새 표준에 추가된 기능 중 유용한 기능에 대해 기록해 볼까 합니다. 우선은 C++ 17에 추가된 내용들을 먼저 정리해 보려고 합니다. 참고로 아직 C++ 23은 정식으로 나온거 같지는 않네요. 그리고 20에서 추가되는 기능도 크지 않을 거라는 전망입니다.
아래에 나열한 기능들은 제 개인적인 의견으로 유용할 것 같은 애들만 기술했습니다. 참고해 주세요~
1. C++ 17에서 유용할 거 같은 애들
- std::optional : 값이 있을 수도 있고 없을 수도 있는 상황, 이를테면 한 자료구조에서 특정 요소를 찾아 해당 요소가 위치하는 index를 반환하는 함수에서 흔히 해당 요소가 존재하지 않을 경우 '-1'을 관행적으로 반환하는 경우가 많습니다. 그리고 -1이 반환되면 이 함수는 실패했다고 간주하자. 라고 사용하게 됩니다. 하지만 만약 다른 기능을 하는 함수인데 -1도 의미가 있는 함수이다. 그러면 어떻게 함수 실패 여부를 판단할 수 있을까요? 이런 경우에 이 std::optional이 유용할 것 같습니다. 아래 예제코드를 먼저 보시죠.
-
#include <iostream> #include <optional> std::optional<int> get_even_number(int num) { if (num % 2 == 0) return num; else return {}; } int main() { auto opt = get_even_number(3); if (opt.has_value()) { std::cout << "Even number: " << opt.value() << '\n'; } else { std::cout << "Not an even number.\n"; } return 0; }
- 위 코드에서 함수 get_even_number()는 주어진 숫자가 짝수인 경우 그 숫자를 반환하고, 홀수인 경우 아무런 값도 반환하지 않습니다. 따라서 이 함수의 반환 타입은 '정수값이 존재할 수도, 존재하지 않을 수도 있는' 상황을 잘 나타내기 위해 std::optional로 설정되었습니다.
따라서 이러한 방식으로 std::optional은 값의 유무에 따른 복잡성을 관리하며, 오류 처리나 특별한 반환 값을 필요로 하는 경우에 코드 가독성과 안정성을 높일 수 있게 돕습니다.
-
- if - initialize : if문과 switch문에 초기화 구문을 추가할 수 있는 기능이 도입되었습니다. 이를 통해 코드의 가독성을 높이고, 변수의 범위를 제한할 수 있습니다. 기본적인 형태는 다음과 같습니다.
-
if (init; condition) { // statements }
- 여기서 init은 초기화 구문이며, condition은 조건식입니다. 예를 들어, C++17 이전에는 아래와 같이 작성해야 하는 코드가 있었습니다.
-
C++17에서 도입된 if-초기화 구문을 사용하면 위의 코드를 아래와 같이 간결하게 작성할 수 있습니다.std::map<int, std::string> m = {{1, "one"}, {2, "two"}, {3, "three"}}; auto itr = m.find(2); if (itr != m.end()) { std::cout << itr->second; }
-
위의 예제에서 보듯이 if-초기화 구문을 사용하면 변수 itr의 범위가 if 문 내로 제한되므로 외부에서 해당 변수를 실수로 변경하는 것을 방지할 수 있습니다. 이런 특징은 코드의 안정성을 높여줍니다.std::map<int, std::string> m = {{1, "one"}, {2, "two"}, {3, "three"}}; if (auto itr = m.find(2); itr != m.end()) { std::cout << itr->second; }
-
- Structured Bindings : 복합 데이터 타입을 여러 변수에 한번에 분해할 수 있는 기능입니다. 이를 통해 코드를 더 간결하고 가독성 있게 작성할 수 있습니다. 기본적인 사용 방법은 다음과 같습니다.
-
여기서 expression은 tuple-like 객체, 구조체, 배열 등이 될 수 있습니다. 예를 들어, pair나 tuple을 반환하는 함수의 결과를 각각의 변수로 바로 분해할 수 있습니다.auto [var1, var2, ..., varN] = expression;
-
std::pair<int, std::string> foo() { return {123, "Hello"}; } int main() { auto [num, str] = foo(); std::cout << num << ", " << str << '\n'; // 출력: 123, Hello }
- 구조체도 마찬가지로 분해할 수 있습니다.
-
struct MyStruct { int num; std::string str; }; MyStruct foo() { return {123, "Hello"}; } int main() { auto [num, str] = foo(); std::cout << num << ", " << str << '\n'; // 출력: 123, Hello }
- 배열 역시 분해 가능합니다.
-
int main() { int arr[] = {1, 2}; auto [a,b] = arr; std::cout<< a <<" "<< b<<'\n'; // 출력 : 1 2 }
- Structured Bindings는 이처럼 복합 데이터 타입을 각각의 변수로 쉽게 분해하고 관리할 수 있게 해주므로 코드의 가독성과 편리성을 크게 높여줍니다.
-
- Nested namespace : C++17에서는 중첩된 네임스페이스를 간결하게 표현할 수 있는 기능이 도입되었습니다. 이전 버전의 C++에서는 네임스페이스를 중첩해서 사용하려면 아래와 같이 작성해야 했습니다.
-
하지만 C++17에서 도입된 Nested Namespace Declaration을 사용하면, 위의 코드를 아래와 같이 간결하게 작성할 수 있습니다.namespace LevelOne { namespace LevelTwo { namespace LevelThree { // 코드 } } }
-
namespace LevelOne::LevelTwo::LevelThree { // 코드 }
- 위의 두 코드는 완전히 동일한 기능을 수행합니다. 즉, LevelOne, LevelTwo, LevelThree라는 세 개의 네임스페이스가 순차적으로 중첩된 것입니다. Nested Namespace Declaration은 중첩된 네임스페이스를 더욱 간결하고 명확하게 표현할 수 있게 해줍니다. 이로 인해 코드의 가독성이 향상되고, 실수로 발생할 수 있는 문제도 줄어들게 됩니다.
-
- std::variant, std::any : C++17에서는 다양한 타입을 다루기 위한 새로운 유틸리티인 std::variant와 std::any가 도입되었습니다.
- std::variant는 합집합 타입(union type)으로, 여러 가지 타입 중 하나를 보관할 수 있는 클래스 템플릿입니다. 각각의 std::variant 객체는 언제든지 그 variant가 가질 수 있는 타입 중 하나를 저장할 수 있습니다.
다음은 std::variant의 사용 예시입니다.-
#include <iostream> #include <string> #include <variant> int main() { std::variant<int, float, std::string> v; v = 10; std::cout << std::get<int>(v) << ' '; // 출력: 10 v = 1.5f; std::cout << std::get<float>(v) << ' '; // 출력: 1.5 v = "Hello"; std::cout << std::get<std::string>(v) << ' '; // 출력: Hello return 0; }
-
- 반면에 std:any, 이름에서 알 수 있듯이, 어떤 타입이든 저장할 수 있는 컨테이너입니다. 이를 통해 동적 타입을 지원하게 됩니다. 하지만 이런 유연성은 안정성을 저해할 수 있으므로 주의해서 사용해야 합니다.
다음은 std:any의 사용 예시입니다.-
여기서 주의할 점은 std:any_cast<>를 사용하여 적절한 형태로 변환해주어야 한다는 것입니다. 만약 잘못된 형태로 변환하려고 시도한다면, 프로그램은 예외(bad_any_cast)를 발생시킵니다.#include <iostream> #include <any> int main() { std::any a = 1; std::cout << "integer: " << std:any_cast<int>(a) << ' '; a = 'a'; std:cout<< "char : "<<std:any_cast<char>(a)<<' '; a=1.5f; std:cout<<"float : "<<std:any_cast<float>(a)<<' '; return 0; }
- std::any는 type safe한 void*에 가까우며 따라서 어떤 형식의 값도 대입이 가능하다. 다만 void* 와 다르게 객체의 적절한 파괴까지 보장해 준다는 점이 차이점이다. 그러나 메모리까지 자동으로 free해주는 것은 물론 아니다.
-
- std::variant는 합집합 타입(union type)으로, 여러 가지 타입 중 하나를 보관할 수 있는 클래스 템플릿입니다. 각각의 std::variant 객체는 언제든지 그 variant가 가질 수 있는 타입 중 하나를 저장할 수 있습니다.
- std::byte : 메모리를 가공할 때 사용하는 타입입니다. 이는 정수형이 아닌, 열거형(enum class)으로 정의되어 있습니다. 따라서 std::byte는 숫자로서의 연산을 수행하지 않고, 비트 단위의 조작만 가능하게 설계되었습니다. 다음은 std::byte에 대한 간단한 예제입니다.
-
#include <cstddef> #include <iostream> int main() { std::byte b{0}; // 초기화 b = std::byte{255}; // 255로 설정 std::cout << static_cast<int>(b) << '\n'; // 출력: 255 b <<= 1; // 왼쪽으로 한 비트 시프트 std::cout << static_cast<int>(b) << '\n'; // 출력: 254 (비트 패턴이 왼쪽으로 한 칸 이동하여 최하위 비트가 0이 됩니다) return 0; }
- 다만 이 std::byte는 windows의 기본 자료형인 byte와 표기가 겹치기 때문에, 아래의 구문을 사용하면 ambiguous symbol에러가 발생할 수 있다.
- using namespace std;
- 따라서 해당 구문을 사용하지 않고 항상 표준 라이브러리 함수나 자료형은 std::xxx 형태로 사용할 것을 권장하지만 기존 source가 해당 구문을 사용하고 있고 수정이 어려운 경우에는 다음 매크로를 stdafx.h등에 삽입한다.
- #define _HAS_STD_BYTE 0
-
- ranged for에 변수 선언 및 초기화 가능 : ranged for에 변수 선언과 초기화가 가능해 졌다. 이를테면 이전에는 ranged for안에 순차적으로 증가하는 값이 요구될 경우 다음과 같이 coding해야 했다.
-
이제는 변수 선언부를 for안에 포섭할 수 있다.int index = 0; for(auto& i : values) { ++i; /* ... */ }
-
따라서 해당 용도로 사용되는 변수의 scope를 제한할 수 있다.for(int index = 0; auto& i : values) { ++i; /* ... */ }
-
기록하다보니 내용이 꽤 길어지내요. 다음번에는 C++ 20에서 주목할 만한 Feature들을 기록해 보겠습니다.
Bye~~
반응형
'c c++ mfc' 카테고리의 다른 글
GFlags 를 이용하여 메모리 누수 찾기 (1) | 2024.01.22 |
---|---|
C++ 20의 주목할만한 Feature (91) | 2023.10.04 |
[Git] Merge vs Rebase, 그 차이점 알아보기 (26) | 2023.09.07 |
UML Class Diagram의 화살표들, Class 간의 관계 (4) | 2023.08.24 |
ADO (ActiveX Data Objects) 이해하기 및 예제 (VBScript, C#, C++) (2) | 2023.08.23 |