Modern C++ Programming 소개

Seongcheol Jeon·2024년 11월 30일
0

CPP

목록 보기
27/47
post-thumbnail

일반적으로 모던 C++이라고 하면, C++11 표준안 이후를 통칭한다.

기존 C++ 표준으로도 프로그램을 만들 수 있지만, 모던 C++에 추가된 개념과 기능을 활용하면 좀 더 효율적이고 안정된 프로그램을 만들 수 있다.

C++ 버전별 주요 특징

C++98은 언어의 기본 골격을 완성한 최초의 표준이다. 언어가 표준화되었다는 것은 언어의 방언(사투리)이 생기지 않는다는 것을 의미한다. 표준에 맞게 작성한 소스 코드는 표준을 지원하는 컴파일러를 사용한다면 종류와 상관없이 컴파일할 수 있다.

C++03C++98에서 발견된 문제점을 수정해서 보강한 것이다. 한 가지 큰 특징이라면 STLC++ 표준 라이브러리로 편입되었다는 점이다.

이후 8년만에 나온 C++11에는 많은 변화가 있었다. 가장 큰 특징은 다른 언어에 있는 foreach와 유사한 범위 기반(range based) for문을 사용할 수 있게 되었고, 람다 표현식이 도입되었다. 그리고 C++ 표준 위원회는 C++11부터 3년 단위로 표준을 제정하기로 했다.
이를 기차 모델(train model)이라고 한다. 표준의 품질도 중요하지만 정기적인 표준안 제정이 더 중요하다고 판단했기 때문이다.

C++14C++11의 수정 사항들을 반영한 버전이다. C++17에서는 파일 시스템과 병렬 처리 라이브러리를 도입했으며 몇 가지 문법에 대한 변화도 있었다.

C++20에서는 바이너리 모듈을 가져오는 모듈(module), 템플릿 매개변수에 제약 조건을 명시할 수 있는 컨셉(concept), 그리고 함수를 수시로 멈췄다가 다시 진행할 수 있는 코루틴(coroutine) 등 많은 많은 변화가 있었다.

C++23은 코로나 19의 영향으로 몇 가지 변경 사항이 C++26으로 순연되었지만, 연역 this 포인터와 컴파일러 최적화 등이 표준으로 채택되었다.

모던 C++ 버전별로 어떠한 변화가 있었는지 살펴보자.


C++11/14 주요 변경 사항

많은 개발자가 C++98/03을 C++언어의 기본 문법과 표준 라이브러리를 제공해 준 기초 버전으로 여기고 있다. C++98/03으로도 많은 프로그램을 개발할 수 있다. 하지만 C++가 처음 만들어진 1984년과 현재의 컴퓨팅 환경은 많이 달라졌다. 특히 프로그래밍 언어 관점에서 본다면 더 편리하고 생산성이 높은 언어들이 유행했다.

C++11/14에는 이러한 변화가 반영되어 기존 C++ 언어와는 매우 다른 문법 요소와 표준 라이브러리가 채택되었다. C++의 장점인 저수준 개발 지원을 유지하면서도 생산성을 높였다.

C++11/14의 주요 변경 사항을 알아보자.

범위 기반 for 문

배열이나 컨테이너를 순회할 때 인덱스(index)반복자(iterator)를 많이 사용한다. 하지만 범위 기반 for문을 사용하면 코드를 간단하게 작성할 수 있다. 범위 기반 for 문(range based for statement)은 파이썬이나 자바, C#에서 지원하는 foreach와 비슷한 문법이다.

자료형 추론 auto

C++03까지는 변수나 함수를 선언할 때 자료형을 지정해야 했지만, C++11 부터는 자료형을 추론할 수 있는 auto가 추가되었다.

널 포인터 리터럴 nullptr

null을 표현할 때 0이나 매크로로 정의된 NULL을 사용했지만, C++11 부터는 nullptr이라는 카워드로 좀 더 명확하게 표현할 수 있게 되었다.

람다 표현식

람다 표현식(lambda expressions)은 소스 코드가 간결해지고 함수의 흐름을 쉽게 파악할 수 있게 한다. 또한 인라인(inline)으로 만들 수 있어서 성능에도 좋은 영향을 줄 수 있다.

override와 final 키워드

C++11부터는 자식 클래스에서 가상 함수를 선언할 때, override 키워드를 추가하면 부모 클래스의 가상 함수를 오버라이딩했음을 명시할 수 있으다.

final을 사용하면 더는 오버라이딩 하지 않음을 명시할 수 있다. 이렇게 하면 의도와 다르게 코드를 작성했을 때, 컴파일 오류를 유도해 적절하게 대처할 수 있다.

스마트 포인터

CC++ 언어는 메모리를 다양한 방식으로 다루고 효율적으로 사용할 수 있다. 하지만 그만큼 다루기 어렵고 구현할 코드가 많다.
C++11에서는 메모리를 자동으로 관리해 주는 스마트 포인터가 도입되었다.

튜플

C++ 함수에서 여러 개의 값을 동시에 반환하려면 구조체를 사용해야 한다. 하지만 최신 스크립트 언어(예: 파이썬, 자바스크립트 등)들은 동시에 여러 개의 값을 다양한 데이터 형식으로 반환하는 기능이 있다.

C++11에서도 튜플(tuples)을 사용하면 이러한 가능을 구현할 수 있다. C++17구조적 바인딩(structured bindings)과 함께 사용하면 스크립트 언어의 사용성을 그대로 경험할 수 있다.


C++17 주요 변경 사항

C++17에서도 많은 변화가 있었다. C++17의 주요 변경 사항을 알아보자.

파일 시스템 라이브러리

C++17부터는 디렉토리와 파일을 다룰 수 있는 방법을 표준 라이브러리로 채택하여 파일 시스템을 사용하는 소스 코드의 운영체제 호환성을 높였다.

병렬 처리 라이브러리

기존에도 병렬 처리를 지원하는 라이브러리는 많았지만 각기 다른 프로그래밍 방식으로 혼란이 있었다. C++17에서는 이러한 혼란을 없애고자 병렬 처리 방식을 표준화했다. 이로써 일관된 방식으로 병렬 처리를 할 수 있다.

if와 switch문 초기화

원래 C++ifswitch, 범위 기반 for 같은 제어문에서 별도의 초기화 구문을 지원하지 않았다. 따라서 초기화 구문을 제어문 밖에 작성해 다른 코드와 구분하기가 어려웠다.

그러나 C++17 부터는 제어문에 초기화 구문을 포함할 수 있게 되었다. 이제 제어문에서 사용할 변수는 외부의 간섭을 받지 않고 독립된 초기화를 수행할 수 있다.

폴드 표현식

가변 인자는 함수의 매개변수 개수나 형식을 미리 지정하지 않고 동적으로 처리하는 방법으로, C언어부터 지원되었다. C++ 언어에서는 가변 인자를 처리할 수 있는 매개 변수 팩(parameter pack)을 추가로 지원한다.

폴드 표현식은 매개변수 팩으로 입력받은 값을 반복해서 처리할 때 재귀 함수를 사용하지 않고 처리할 수 있는 문법으로, 메모리 문제나 종료 조건 설정에 대한 실수를 방지할 수 있다.

구조적 바인딩

C++11/14에서 결과값을 여러 개 반환할 수 있는 튜플을 지원했지만 사용법이 간단하지 않았다. 구조적 바인딩(structured bindings)은 이를 보완하여 함수의 반환값에서 튜플을 즉시 사용할 수 있도록 지원하는 문법이다.


C++20 주요 변경 사항

C++20에서는 현대 언어로 탈바꿈이라고 할 수 있을 정도로 굵직한 기능들이 추가되었다. C++20의 주요 변경 사항을 알아보자.

컨셉 (Concepts)

컨셉(concepts)은 함수 템플릿 매개변수의 종류나 속성에 대한 제약을 명시하는 기능이다. 템플릿 사용이 문법적으로 올바르더라도 데이터 형식이나 연산자 미지원 등으로 컴파일 오류가 발생할 수 있다. 이때 컨셉을 사용하여 제약 조건을 명시함으로써 코드를 더 명확하게 만들고 잘못된 템플릿 인자로 인한 컴파일 오류를 사전에 방지할 수 있다.

코루틴 (Coroutine)

CC++언어에서 모든 함수는 시작과 종료가 한 번씩 수행된다. 한 번 종료된 함수가 다시 호출될 때에는 처음부터 다시 시작한다. 이러한 루틴을 가지는 일반 함수를 서브 루틴(subroutine)이라고도 한다.

반면에 코루틴(coroutines)은 함수를 멈췄다가 다시 호출할 수 있는 루틴을 말한다. 코루틴은 다시 호출될 때 중단된 시점부터 재개된다.

코루틴 함수는 한꺼번에 실행하지 않고, 루틴을 조절해 가며 그때그때 필요한 기능을 실행하거나 데이터를 반환받을 수 있다. 특히 코루틴이 종료되기 전까지 지역 변수가 유지되므로 전역 변수를 사용하지 않고도 여러 호출부에서 값을 공유할 수 있다.

모듈 (Module)

소스 파일에서 외부 모듈을 사용하려면 #include 문으로 필요한 헤더 파일을 가져온다. 이때 헤더 파일에 선언된 내용과 현재 소스 파일의 선언이 중복되지 않도록 처리해야 한다. 그리고 가져온 헤더 파일과 관련된 소스 파일을 컴파일하거나 오브젝트 코드를 링크해야 하므로 그만큼 컴파일 시간이 소요된다.

그러나 모듈(module)을 사용하면 외부 모듈을 가져올 때 선언이 중복되는 것을 개발자가 별도로 신경 쓸 필요가 없고, 이미 컴파일 된 모듈을 가져올 수 있어 시간도 줄일 수 있다. 예를 들어 #include <isostream> 대신 모듈을 사용할 때는 import std.core로 대체하면 된다.

3방향 비교 연산자 (Three-way comparison)

3방향 비교(three-way comparison) 연산자는 이름과 다르게 이항 연산자이다. 다른 이항 비교 연산자는 한 가지 비교만 할 수 있지만, 3방향 비교 연산자는 작음(<), 같음(=), 큼(>) 등 3가지 방향을 조합해 총 5가지 비교를 한번에 할 수 있다.

연산자 오버로딩을 사용하면 숫자뿐만 아니라 클래스나 다양한 형식을 비교할 수도 있다. 3방향 비교 연산자는 모양 때문에 우주선 연산자(<=>)라고도 부른다.

수학 상수

C++ 언어로 과학 기술 계산을 하다 보면 무한 소수로 된 상수를 사용하게 된다. 대표적으로 자연로그 e, 원주율 pi, 루트2, 루트3 과 같은 상수가 있다. 보통은 매크로로 정의하거나 수학 라이브러리를 사용했지만, C++20 부터는 미리 정의된 수학 상수를 사용해 여러 개발자가 공통의 수학 정의를 사용할 수 있게 되었다.


C++23 주요 변경 사항

연역 this 포인터

연역 this는 이해와 사용이 매우 어려운 문법이지만, C++23에 추가된 가장 큰 변화이다.

클래스의 멤버 함수에서 사용하는 this는 컴파일러가 자동으로 추가하는 암묵적 객체 매개변수(implicit object parameter)이다. 객체 스스로를 가리키는 this 포인터를 사용하면 클래스나 구조체의 멤버에 접근해 값을 변경할 수 있다. 만약 변경할 수 없게 하거나(const) 다른 제약 사항을 추가하고 싶을 때는 연역(deducing) this 포인터를 사용한다.

연역 this 포인터는 보통 self라는 이름으로 표현하는데, 읽기 전용으로 선언하거나 레퍼런스로 전달된 thisself라는 이름으로 사용한다.

self는 Python 같은 언어에서 명시적 객체 매개변수의 이름으로 사용된다.

모나드 형식 optional, expected

모나드(monad)는 함수형 언어에서 사용되는 디자인 패턴이다. C++17에 추가된 optionalexpected 키워드는 C++23부터 모나드 데이터 형식으로 사용할 수 있다. 이 두 가지 데이터 형식은 오류 처리와 값의 존재 여부를 효과적으로 다룰 수 있다.

가변 인자 출력 print

C++에서 cout으로 여러 변수를 출력하려면 소스 코드가 지나치게 길어진다. 이때 C언어 형식의 printf를 사용할 수 있다. 그런데 C++23 표준 라이브러리에 추가된 print 함수를 사용하면 좀 더 쉬운 방법으로 가변 인자를 출력할 수 있다.

print 함수는 가변 인자를 표현할 수 있는 std::format(C++20에서 추가됨)을 출력하는 방식이며, printf를 사용하는 기존 방식보다 가변 인자를 편리하게 출력할 수 있다.


현대적 관점의 C++

C++언어의 시작은 C언어에 객체지향 개념을 추가한 것이다. 그래서 초기에는 C언어세 객체지향 요소만 추가한 문법 위주로 C++를 사용했다. 하지만 C++26에는 모던 C++를 기본으로 사용하는 것이 최신 프로그래밍 환경에 더 적합하다.

이와 관련된 좋은 강의를 소개한다. 케이트 그레고리(Kate Gregory)CppCon 2015에서 발표한 <Stop Teaching C>이다. 영어로 말하는 강의지만 C++ 문법을 제시한 자료만 보더라도 내용을 쉽게 이해할수 있다.
이 영상은 요지는 *C++ 언어를 조금 더 C++답게 사용하자! 이다.

char* 대신 string, []array 대신 vector<> 사용하기

C++ 언어에서 문자열 처리는 무척 까다롭다. char*를 사용할 때에는 할당받은 메모리 크기를 고려하면서 사용해야 하지만, 표준 라이브러리의 문자열 데이터인 string은 사용 방법도 쉽고 다양한 문자열 데이터 형식으로 변경할 수 있다. 배열 대신 vector를 사용한느 것도 많은 이점이 있다. 메모리 문제에서도 자유로워질 수 있다. 처음정한 메모리를 재조정하지 않고도 범위에 상관없이 자유롭게 사용할 수 있다. 그리고 범위 기반 for문을 사용할 수 있다.

포인터 대신 레퍼런스 사용하기

객체를 참조하는 포인터 대신 레퍼런스를 사용한다. 포인터는 선언과 사용, 인자를 전달하는 등의 코드를 작성할 때에 *, ->, & 연산자를 사용해야 해서 복잡하고 어렵다.
반면에 레퍼런스는 매개변수와 변수 선언에서만 &를 사용하면 되므로 C++ 언어를 처음 배우는 사람도 쉽게 이해할 수 있다.

메모리 직접 관리 대신 RAII 사용하기

메모리 직접 관리는 C++ 언어의 큰 장점이자 단점이기도 하다. 항상 newdelete 키워드 쌍을 만들어야 하던 방식이 아닌 RAII를 사용해, 선언 범위를 벗어나면 메모리가 자동으로 해제될 수 있도록 한다.

정리

구분
내용
데이터 형식수학 상수, auto, enum, constexpr, nullptr, 유니폼 초기화, std::function, 이진수 리터럴, 숫자 구분 기호
구문튜플/구조적 바인딩, 범위 기반 for 문, 정규 표현식, if/switch 문 초기화, 폴디 표현식, 3방향 비교 연산자, using, noexcept, 함수 키워드(delete, defualt, override, final)
표준 라이브러리파일 시스템, 실행 정책(순차, 병렬 처리)
신규 개념스마트 포인터, 람다 표현식, 코루틴, 모듈, 컨셉, 무브 시멘틱, 명시적 오버라이드, 기능 테스트 매크로, 연역 this

0개의 댓글