Welcome back to C++ - Modern C++
Describes the new programming idioms in Modern C++ and their rationale.
docs.microsoft.com
c++은 탄생 이후로, 세계에서 가장 넓게 사용되는 프로그래밍 언어입니다. 잘 작성된 c++ 프로그램은 빠르고 효율적입니다. 다른 언어보다 더 유연합니다. 높은 수준의 추상화와 저수준 실리콘? 작업이 가능합니다. c++ 높은 수준의 최적화 표준 라이브러리를 제공합니다. 이것은 저수준 하드웨어에 접근 가능하게하고 속도를 최대하 하고, 메모리 사용을 최소하 시킵니다. c++을 사용하면 당신은 다양합 앱을 만들 수 있습니다. 게임, 장치 드라이버, 고성능 과학 프로그램, 임베디드 프로그램, 윈도우 앱. 심지어 다른 언어를 위한 라이브러리나 컴파일러 까지 c++로 작성됩니다.
c++의 본래 요구사항 중 하나는 c언와의 역호환성이였습니다. 결과적으로, c++은 저수준 포인터, 배열, 널 제거 캐릭터 스트링?, 기타 등등 c언어의 프로그래밍 스타일을 항상 지원하고 있습니다. 그것은 훌륭한 퍼포먼스를 발휘할 수도 있지만, 또한 버그와 복잡성을 유발 시킬 수 있다. c++ 진화는 c 스타일의 관용어의 필요성을 크게 줄이는 것을 강조해 왔습니다. 오래된 c 프로그래밍 장치들은 당신이 필요할 때 사용할 수 있습니다. 그러나 모던c++ 쓸 때는 c를 사용을 최대한 줄여야합니다. 모던 c++코드는 간단하고, 안전하면서, 더 우아하고, 여전히 빠릅니다.
Resources and smart pointers
C스타일 프로그래밍 버그 중 가장 주요한 클래스중 하나는 메모리 leak 이다. leak은 new로 할당된 메모리에 대해서 delete 하는 것이 실패되었을 때 자주 발생한다. 모던 c++은 RAII(Resource acquisition is initialization) 원리를 강조한다. 그 생각은 간단한것 입니다. 자원(힙 메모리, 파일 처리, 소켓, 등)은 object에 의해 소유 됩니다. 그 object는 생성자에서 새로 할당된 자원을 생성하거나 받고, 소멸자에서 삭제합니다. RAII의 원리는 오브젝트 소유권이 범위를 벗어 났을때 운영체제에게 적절히 반환되도록 보장하는 것이다.
RAII 원리의 손쉬운 채택을 지원하기 위해서 c++ 표준 라이브러리는 세가지 스마트 포인터 타입을 제공 합니다. std::unique_ptr,std::shared_ptr, and std::weak_ptr. 스마트 포인터는 본인이 소유한 메모리의 할당과, 제거를 다룹니다. 다음 예제는 make_unique() 호출로 힙에 배열 멤버가 할당된 클래스를 보여줍니다. new와 delete의 호출은 unique_ptr 클래스에 내포되어 있습니다. widget 오브젝트가 범위를 벗어나면 유니크 포인터 소멸자는 호출되고 그것은 배열에 할당되어 있는 메모리를 release 시킵니다.
#include <memory>
class widget
{
private:
std::unique_ptr<int> data;
public:
widget(const int size) { data = std::make_unique<int>(size); }
void do_something() {}
};
void functionUsingWidget() {
widget w(1000000); // lifetime automatically tied to enclosing scope
// constructs w, including the w.data gadget member
// ...
w.do_something();
// ...
} // automatic destruction and deallocation for w and w.data
가능하면, 힙메모리를 사용할때 스마트 포인터를 사용해라. 당신이 반드시 new, delete 연산을 명시적으로 사용해야 한다면 RAII 원칙을 따르세요. 더 많은 정보를 여기를 참고하세요. 오브젝트 수명과 자원 관리(RAII)
std::string and std:;string_view
C 스타일 문자열은 또다른 주요 버그 원인이다. std;;string and std::wstring을 사용하면 당신은 C스타일 문자열과 관련된 가상의 모든 에러들을 제거할 수 있다. 당신은 또 Searching, appending, prepending 등 멤버 함수의 이점을 얻을 수 있다. 둘다 매우 속도면에서 최적화 되어 있다. 읽기나 쓰기 전용으로 함수에 사용되면 c++17에서는 당신은 std::string_view 를 사용할 수있다. 이는 훨씬 더 강력한 이점을 갖는다.
std::vector and other Standard Library containers
표준 라이브러리 컨테이너는 모두 RAII 원칙을 따릅니다. 그들은 요소들의 안전한 순회를 위해 iterators를 제공합니다. 그리고 매우 최적화 되어 있으며, 철저하게 정확성에 대해 테스트 되었습니다. 이러한 컨테이너를 사용하면 당신은 커스텀 자료구조에서 제시되는 잠재적인 버그나 비효율성을 제거할 수 있습니다. raw array 대신에 c++ 선형 컨테이너인 vector를 사용하세요.
vector<string> apples;
apples.push_back("Granny Smith");
기본 관계형 컨테터인 map(unordered_map 아님)을 사용하세요. 비재생성과 다양한 경우에 set, multimap, multiset을 사용하세요.
map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";
성능상 최적화가 필요할때, 다음을 고려해라
- 내장 배열 타입은 클래스 멤버와 같이 쓰일 때, 중요합니다.
- unordered_map은 비정렬 관계형 컨테이너이다. 요소당 더 적은 오버헤드와 더 적은 지속성 시간 조회를 같지만, 정확하고 효율적으로 사용하기 어렵습니다.
- 정렬된 vector. 더 많은 정보는, 참고 하세요 Algorithms.
C 스타일 배열을 사용하지 마세요. 데이터에 직접 접근이 필요로 하는 오래된 api인 경우 f(vec.data(), vec.size()); 와 같은 접근자 함수를 사용하세요. 컨테이너에 대한 더 많은 정보는 참고하세요 Standard Library Containers.
Standard Library algorithms
당신이 당신 프로그램에 자체제작 알고리즘을 작성하기 전에 먼저 C++ 표준 알고리즘 라이브러리를 검토해라. 표준 라이브러리는 탐색, 정렬, 필터, 램덤과 같은 많은 공통적인 연산에 대한 꾸준히 증가하는 알고리즘 모음입니다. 수학 라이브러리는 광범위합니다. C++17이 시작되면서 많은 알고리즘 버전 또한 제공됩니다.
아래 몇가지 중요한 샘플이 있다
- for_each, 기본적인 순회 알고리즘( for 반복 범위에 따라)
- transform, 컨테이너 요소의 not-in-place 수정용
- find_if, 기본 탐색 알고리즘
- sort, lower_bound, 그리고 다른 정렬, 탐색 알고리즘들
비교자를 쓰기 위해서는 <를 사용해라 그리고 명명 람다를 사용해라
auto comp = [](const widget& w1, const widget& w2)
{ return w1.weight() < w2.weight(); }
sort( v.begin(), v.end(), comp );
auto i = lower_bound( v.begin(), v.end(), comp );
auto instead of explicit type names(명시적인 타입 이름을 대신 하는 auto)
C++11 은 변수, 함수 그리고 템플릿 선언에 사용되는 auto 키워드를 소개 했습니다. auto는 컴파일러에게 오브젝트으 타입을 추론하도록 합니다. 그래서 당신은 타입을 명시하지 않아도 됩니다. auto는 특히 추론되는 타입이 nested 탬플릿일 때 유용합니다.
Range-based for loops
C 스타일의 배열이나 컨테이너에 대한 반복은 인텍스 에러를 발생시키기 쉽고, 또한 이걸 타이밍하는 것은 지루한 일이다. 이러한 에러를 제거하고 더 읽기 좋은 코드를 만들기 위해서 표준 라이브러리 컨터네이너와 기존 배열을 지원하는 범위기반 for 반복문을 사용해라. 더 많은 정보는 여기를 참고하세요 Range-based for statement.
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v {1,2,3};
// C-style
for(int i = 0; i < v.size(); ++i)
{
std::cout << v[i];
}
// Modern C++:
for(auto& num : v)
{
std::cout << num;
}
}
constexpr expressions instead of macros
C와 C++ 매크로는 컴파일전에 전처리기에 위해서 처리되는 토큰이다. 각각의 매크로 토큰은 파일이 컴파일 되기전에 정의된 값이나 표현식으로 교체된다. 매크로는 공통적으로 컴파일 타임에서 상수값을 정의하기 위한 c스타일 프로그래밍에서 사용되었다. 하지만 매크로는 에러를 일으키고 디버깅하기 어렵다. 모던 c++에서는 컴파일 상수를 위해 constexpr 변수를 사용하는것이 더 좋다.
#define SIZE 10 // C-style
constexpr int size = 10; // modern C++
Uniform 초기화
모던 c++ 에서는 당신은 어떤 타입이든 괄호 초기화를 사용할 수 있습니다. 이 초기화 형식은 특히 배열이나 벡터 또는컨테이너를 초기화할때 효과적입니다. 예를 들어 v2는 3개의 인스턴스로 초기화 됩니다. v3는 괄호를 사용해서 초기화된 S 인스턴스를 3개로 초기화됩니다. 컴파일러는 v3의 선언된 타입을 통해 각요소 타입을 추론합니다.
#include <vector>
struct S
{
std::string name;
float num;
S(std::string s, float f) : name(s), num(f) {}
};
int main()
{
// C-style initialization
std::vector<S> v;
S s1("Norah", 2.7);
S s2("Frank", 3.5);
S s3("Jeri", 85.9);
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
// Modern C++:
std::vector<S> v2 {s1, s2, s3};
// or...
std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };
}
더많은 정보는 괄호 초기화를 보세요.
Move semantices(이동 의미론?)
모던 C++은 move semantice를 제공합니다. move semantics는 불필요한 메모리 복사를 제거하는 것을 가능하게 합니다. 이전 버전 언어에서는 특정 상황에서 복사는 피할 수 없었습니다. move 연산은 자원의 소유권을 복사 없이 다른 객체로 전달합니다. 일부 클래스는 힙메모리, 파일 처리 등의 자원을 소유합니다. 당신이 자원 소유 클래스를 구현할 때, 당신은 move 생성자와 move 할당연산을 정의할 수 있습니다. 컴파일러는 복사가 필요하지 않는 상황에서 오버로드 해결책에서 특별한 멤버들을 선택합니다. 표준 컨테이너 유형은 정의 되어 있다면 move 생성자를 실행합니다. 더 많은 정보는 여기를 보세요. Move Consructors and Move Assignment Operators(C++)
람다 표현식
C스타일 프로그래밍에서 함수는 함수 포인터를 사용해서 다른 함수로 전달될 수 있습니다. 함수 포인터는 유지하고 이해하기 불편합니다. 그 함수는 그 함수가 실행되는 위치에서 매우 멀리 떨어진 소스코드에 정의되어 있을 것입니다. 그리고 그 함수들은 안전한 타입이 아닙니다. 모던 c++은 함수 객체, operator()를 오버라이드하는 클래스를 제공합니다. 그것은 그들을 함수처럼 불리는게 가능하게 합니다.함수 객체를 만드는 가장 편한 방법은 인라인 람다 표현식입니다. 다음 예제는 함수 객체를 전달하기 위해서 어떻게 람다 표현식을 쓰는지 보여줍니다. for_each 함수는 벡터의 각 요소를 실행 시킵니다.
std::vector<int> v {1,2,3,4,5};
int x = 2;
int y = 4;
auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });
이 람다 표현식 [=](int i){return i > x && i < y; } 은 함수는 "하나의 int 매개변수를 갖고 매개변수가 x보다 크고 y보다 작은 것인지를 나타내는 boolean값을 반환합니다. "라고 해석됩니다. 여기서 주목할 점은 주변 켄텍스트 변수인 x,y는 람다 안에서 사용될수 있습니다. [=] 는 이러한 값들을 캡쳐 되도록 지정합니다. 즉, 람다 표현식은 이런한 값들의 복사본을 갖습니다.
Exceptions
모던 C++은 에러를 처리하고 보고하는 최고의 방법으로 에러코드 보다 예외를 강조합니다. 더 많은 정보는 여길 보세요. Morden C++ Best practices for exceptions and error handling.
std::atmoic
내부 스레드 통신 메커님에 대해서는 C++ 표준 라이브러리 std::atomic 구초제나 관련 유형을 사용하세요.
std:;variant(C++17)
유니온은 보통 C스타일 프로그래밍에서 다른 유형의 멤버타입을 같은 공간을 차지하게 함으로서 메모리 보존을 위해서 사용됩니다. 그러나 유니온은 안전한 방법이 아니고 프로그래밍 에러를 일으킵니다. C++17은 더 강력하고 유니온에 비해 비교적 안전한 클래스인 std:;variant를 제안합니다. std:;visit 함수는 안전한 방식으로 variant 유형 멤버를 접근하는데 사용될 수 있습니다.
See also
C++ Language Reference
Lambda Expressions
C++ Standard Library
Microsoft C/C++ language conformance
'English > Development' 카테고리의 다른 글
MSDN_C++LanguageReference_Identifiers(C++) (0) | 2021.08.19 |
---|---|
MSDN_C++LanguageReference_Comments(C++) (0) | 2021.08.19 |
MSDN_C++LanguageReference_Tokens and character sets (0) | 2021.08.18 |
MSDN_C++LanguageReference_Lexical conventions (0) | 2021.08.18 |
MSDN_C++LanguageReference_Index (0) | 2021.08.09 |