일반적으로
모던 C++
이라고 하면,C++11
표준안 이후를 통칭한다.
기존 C++ 표준으로도 프로그램을 만들 수 있지만, 모던 C++에 추가된 개념과 기능을 활용하면 좀 더 효율적이고 안정된 프로그램을 만들 수 있다.
C++98
은 언어의 기본 골격을 완성한 최초의 표준이다. 언어가 표준화되었다는 것은 언어의 방언(사투리)이 생기지 않는다는 것을 의미한다. 표준에 맞게 작성한 소스 코드는 표준을 지원하는 컴파일러를 사용한다면 종류와 상관없이 컴파일할 수 있다.
C++03
은 C++98
에서 발견된 문제점을 수정해서 보강한 것이다. 한 가지 큰 특징이라면 STL
이 C++ 표준 라이브러리
로 편입되었다는 점이다.
이후 8년만에 나온 C++11
에는 많은 변화가 있었다. 가장 큰 특징은 다른 언어에 있는 foreach
와 유사한 범위 기반(range based) for문
을 사용할 수 있게 되었고, 람다 표현식
이 도입되었다. 그리고 C++
표준 위원회는 C++11
부터 3년 단위로 표준을 제정하기로 했다.
이를 기차 모델(train model)
이라고 한다. 표준의 품질도 중요하지만 정기적인 표준안 제정이 더 중요하다고 판단했기 때문이다.
C++14
는 C++11
의 수정 사항들을 반영한 버전이다. C++17
에서는 파일 시스템과 병렬 처리 라이브러리를 도입했으며 몇 가지 문법에 대한 변화도 있었다.
C++20
에서는 바이너리 모듈을 가져오는 모듈(module)
, 템플릿 매개변수에 제약 조건을 명시할 수 있는 컨셉(concept)
, 그리고 함수를 수시로 멈췄다가 다시 진행할 수 있는 코루틴(coroutine)
등 많은 많은 변화가 있었다.
C++23
은 코로나 19의 영향으로 몇 가지 변경 사항이 C++26
으로 순연되었지만, 연역 this 포인터와 컴파일러 최적화 등이 표준으로 채택되었다.
모던 C++ 버전별로 어떠한 변화가 있었는지 살펴보자.
많은 개발자가 C++98/03
을 C++언어의 기본 문법과 표준 라이브러리를 제공해 준 기초 버전으로 여기고 있다. C++98/03
으로도 많은 프로그램을 개발할 수 있다. 하지만 C++가 처음 만들어진 1984년과 현재의 컴퓨팅 환경은 많이 달라졌다. 특히 프로그래밍 언어 관점에서 본다면 더 편리하고 생산성이 높은 언어들이 유행했다.
C++11/14
에는 이러한 변화가 반영되어 기존 C++ 언어와는 매우 다른 문법 요소와 표준 라이브러리가 채택되었다. C++의 장점인 저수준 개발 지원을 유지하면서도 생산성을 높였다.
C++11/14
의 주요 변경 사항을 알아보자.
배열이나 컨테이너를 순회할 때 인덱스(index)
나 반복자(iterator)
를 많이 사용한다. 하지만 범위 기반 for문을 사용하면 코드를 간단하게 작성할 수 있다. 범위 기반 for 문(range based for statement)
은 파이썬이나 자바, C#에서 지원하는 foreach
와 비슷한 문법이다.
C++03
까지는 변수나 함수를 선언할 때 자료형을 지정해야 했지만, C++11
부터는 자료형을 추론할 수 있는 auto
가 추가되었다.
null
을 표현할 때 0
이나 매크로로 정의된 NULL
을 사용했지만, C++11
부터는 nullptr
이라는 카워드로 좀 더 명확하게 표현할 수 있게 되었다.
람다 표현식(lambda expressions)
은 소스 코드가 간결해지고 함수의 흐름을 쉽게 파악할 수 있게 한다. 또한 인라인(inline)
으로 만들 수 있어서 성능에도 좋은 영향을 줄 수 있다.
C++11
부터는 자식 클래스에서 가상 함수를 선언할 때, override
키워드를 추가하면 부모 클래스의 가상 함수를 오버라이딩했음을 명시할 수 있으다.
final
을 사용하면 더는 오버라이딩 하지 않음을 명시할 수 있다. 이렇게 하면 의도와 다르게 코드를 작성했을 때, 컴파일 오류를 유도해 적절하게 대처할 수 있다.
C
와 C++
언어는 메모리를 다양한 방식으로 다루고 효율적으로 사용할 수 있다. 하지만 그만큼 다루기 어렵고 구현할 코드가 많다.
C++11
에서는 메모리를 자동으로 관리해 주는 스마트 포인터가 도입되었다.
C++
함수에서 여러 개의 값을 동시에 반환하려면 구조체를 사용해야 한다. 하지만 최신 스크립트 언어(예: 파이썬, 자바스크립트 등
)들은 동시에 여러 개의 값을 다양한 데이터 형식으로 반환하는 기능이 있다.
C++11
에서도 튜플(tuples)
을 사용하면 이러한 가능을 구현할 수 있다. C++17
의 구조적 바인딩(structured bindings)
과 함께 사용하면 스크립트 언어의 사용성을 그대로 경험할 수 있다.
C++17
에서도 많은 변화가 있었다. C++17
의 주요 변경 사항을 알아보자.
C++17
부터는 디렉토리와 파일을 다룰 수 있는 방법을 표준 라이브러리로 채택하여 파일 시스템을 사용하는 소스 코드의 운영체제 호환성을 높였다.
기존에도 병렬 처리를 지원하는 라이브러리는 많았지만 각기 다른 프로그래밍 방식으로 혼란이 있었다. C++17
에서는 이러한 혼란을 없애고자 병렬 처리 방식을 표준화했다. 이로써 일관된 방식으로 병렬 처리를 할 수 있다.
원래 C++
는 if
나 switch
, 범위 기반 for
같은 제어문에서 별도의 초기화 구문을 지원하지 않았다. 따라서 초기화 구문을 제어문 밖에 작성해 다른 코드와 구분하기가 어려웠다.
그러나 C++17
부터는 제어문에 초기화 구문을 포함할 수 있게 되었다. 이제 제어문에서 사용할 변수는 외부의 간섭을 받지 않고 독립된 초기화를 수행할 수 있다.
가변 인자는 함수의 매개변수 개수나 형식을 미리 지정하지 않고 동적으로 처리하는 방법으로, C
언어부터 지원되었다. C++
언어에서는 가변 인자를 처리할 수 있는 매개 변수 팩(parameter pack)
을 추가로 지원한다.
폴드 표현식
은 매개변수 팩으로 입력받은 값을 반복해서 처리할 때 재귀 함수를 사용하지 않고 처리할 수 있는 문법으로, 메모리 문제나 종료 조건 설정에 대한 실수를 방지할 수 있다.
C++11/14
에서 결과값을 여러 개 반환할 수 있는 튜플을 지원했지만 사용법이 간단하지 않았다. 구조적 바인딩(structured bindings)
은 이를 보완하여 함수의 반환값에서 튜플을 즉시 사용할 수 있도록 지원하는 문법이다.
C++20
에서는 현대 언어로 탈바꿈이라고 할 수 있을 정도로 굵직한 기능들이 추가되었다. C++20
의 주요 변경 사항을 알아보자.
컨셉(concepts)
은 함수 템플릿 매개변수의 종류나 속성에 대한 제약을 명시하는 기능이다. 템플릿 사용이 문법적으로 올바르더라도 데이터 형식이나 연산자 미지원 등으로 컴파일 오류가 발생할 수 있다. 이때 컨셉을 사용하여 제약 조건을 명시함으로써 코드를 더 명확하게 만들고 잘못된 템플릿 인자로 인한 컴파일 오류를 사전에 방지할 수 있다.
C
와 C++
언어에서 모든 함수는 시작과 종료가 한 번씩 수행된다. 한 번 종료된 함수가 다시 호출될 때에는 처음부터 다시 시작한다. 이러한 루틴을 가지는 일반 함수를 서브 루틴(subroutine)
이라고도 한다.
반면에 코루틴(coroutines)
은 함수를 멈췄다가 다시 호출할 수 있는 루틴을 말한다. 코루틴
은 다시 호출될 때 중단된 시점부터 재개된다.
코루틴 함수
는 한꺼번에 실행하지 않고, 루틴을 조절해 가며 그때그때 필요한 기능을 실행하거나 데이터를 반환받을 수 있다. 특히 코루틴
이 종료되기 전까지 지역 변수가 유지되므로 전역 변수
를 사용하지 않고도 여러 호출부에서 값을 공유할 수 있다.
소스 파일에서 외부 모듈을 사용하려면 #include
문으로 필요한 헤더 파일을 가져온다. 이때 헤더 파일에 선언된 내용과 현재 소스 파일의 선언이 중복되지 않도록 처리해야 한다. 그리고 가져온 헤더 파일과 관련된 소스 파일을 컴파일하거나 오브젝트 코드를 링크해야 하므로 그만큼 컴파일 시간이 소요된다.
그러나 모듈(module)
을 사용하면 외부 모듈을 가져올 때 선언이 중복되는 것을 개발자가 별도로 신경 쓸 필요가 없고, 이미 컴파일 된 모듈을 가져올 수 있어 시간도 줄일 수 있다. 예를 들어 #include <isostream>
대신 모듈을 사용할 때는 import std.core
로 대체하면 된다.
3방향 비교(three-way comparison)
연산자는 이름과 다르게 이항 연산자
이다. 다른 이항 비교 연산자는 한 가지 비교만 할 수 있지만, 3방향 비교
연산자는 작음(<)
, 같음(=)
, 큼(>)
등 3가지 방향을 조합해 총 5가지 비교를 한번에 할 수 있다.
연산자 오버로딩을 사용하면 숫자뿐만 아니라 클래스나 다양한 형식을 비교할 수도 있다. 3방향 비교 연산자
는 모양 때문에 우주선 연산자(<=>)
라고도 부른다.
C++
언어로 과학 기술 계산을 하다 보면 무한 소수로 된 상수를 사용하게 된다. 대표적으로 자연로그 e
, 원주율 pi
, 루트2
, 루트3
과 같은 상수가 있다. 보통은 매크로로 정의하거나 수학 라이브러리를 사용했지만, C++20
부터는 미리 정의된 수학 상수를 사용해 여러 개발자가 공통의 수학 정의를 사용할 수 있게 되었다.
연역 this
는 이해와 사용이 매우 어려운 문법이지만, C++23
에 추가된 가장 큰 변화이다.
클래스의 멤버 함수에서 사용하는 this
는 컴파일러가 자동으로 추가하는 암묵적 객체 매개변수(implicit object parameter)
이다. 객체 스스로를 가리키는 this 포인터
를 사용하면 클래스나 구조체의 멤버에 접근해 값을 변경할 수 있다. 만약 변경할 수 없게 하거나(const)
다른 제약 사항
을 추가하고 싶을 때는 연역(deducing) this 포인터
를 사용한다.
연역 this 포인터
는 보통 self
라는 이름으로 표현하는데, 읽기 전용
으로 선언하거나 레퍼런스로 전달된 this
를 self
라는 이름으로 사용한다.
self
는 Python 같은 언어에서 명시적 객체 매개변수의 이름으로 사용된다.
모나드(monad)
는 함수형 언어에서 사용되는 디자인 패턴
이다. C++17
에 추가된 optional
과 expected
키워드는 C++23
부터 모나드 데이터 형식
으로 사용할 수 있다. 이 두 가지 데이터 형식은 오류 처리와 값의 존재 여부를 효과적으로 다룰 수 있다.
C++
에서 cout
으로 여러 변수를 출력하려면 소스 코드가 지나치게 길어진다. 이때 C
언어 형식의 printf
를 사용할 수 있다. 그런데 C++23
표준 라이브러리에 추가된 print
함수를 사용하면 좀 더 쉬운 방법으로 가변 인자를 출력할 수 있다.
print
함수는 가변 인자를 표현할 수 있는 std::format(C++20에서 추가됨)
을 출력하는 방식이며, printf
를 사용하는 기존 방식보다 가변 인자를 편리하게 출력할 수 있다.
C++
언어의 시작은 C
언어에 객체지향 개념
을 추가한 것이다. 그래서 초기에는 C
언어세 객체지향 요소만 추가한 문법 위주로 C++
를 사용했다. 하지만 C++26
에는 모던 C++
를 기본으로 사용하는 것이 최신 프로그래밍 환경에 더 적합하다.
이와 관련된 좋은 강의를 소개한다. 케이트 그레고리(Kate Gregory)
가 CppCon 2015
에서 발표한 <Stop Teaching C>이다. 영어로 말하는 강의지만 C++
문법을 제시한 자료만 보더라도 내용을 쉽게 이해할수 있다.
이 영상은 요지는 *C++ 언어를 조금 더 C++답게 사용하자! 이다.
C++
언어에서 문자열 처리는 무척 까다롭다. char*
를 사용할 때에는 할당받은 메모리 크기를 고려하면서 사용해야 하지만, 표준 라이브러리의 문자열 데이터인 string
은 사용 방법도 쉽고 다양한 문자열 데이터 형식으로 변경할 수 있다. 배열 대신 vector
를 사용한느 것도 많은 이점이 있다. 메모리 문제에서도 자유로워질 수 있다. 처음정한 메모리를 재조정하지 않고도 범위에 상관없이 자유롭게 사용할 수 있다. 그리고 범위 기반 for문을 사용할 수 있다.
객체를 참조하는 포인터 대신 레퍼런스를 사용한다. 포인터는 선언과 사용, 인자를 전달하는 등의 코드를 작성할 때에 *
, ->
, &
연산자를 사용해야 해서 복잡하고 어렵다.
반면에 레퍼런스
는 매개변수와 변수 선언에서만 &
를 사용하면 되므로 C++
언어를 처음 배우는 사람도 쉽게 이해할 수 있다.
메모리 직접 관리는 C++
언어의 큰 장점이자 단점이기도 하다. 항상 new
와 delete
키워드 쌍을 만들어야 하던 방식이 아닌 RAII
를 사용해, 선언 범위를 벗어나면 메모리가 자동으로 해제될 수 있도록 한다.
구분 | 내용 |
---|---|
데이터 형식 | 수학 상수, auto, enum, constexpr, nullptr, 유니폼 초기화, std::function, 이진수 리터럴, 숫자 구분 기호 |
구문 | 튜플/구조적 바인딩, 범위 기반 for 문, 정규 표현식, if/switch 문 초기화, 폴디 표현식, 3방향 비교 연산자, using, noexcept, 함수 키워드(delete, defualt, override, final) |
표준 라이브러리 | 파일 시스템, 실행 정책(순차, 병렬 처리) |
신규 개념 | 스마트 포인터, 람다 표현식, 코루틴, 모듈, 컨셉, 무브 시멘틱, 명시적 오버라이드, 기능 테스트 매크로, 연역 this |