Modern C++ Summary (vs. C#)
개인적으로 공부하면서, C#과 같이 사용하면서 헷갈릴 수 있는 부분(C++과 다른 점)과 C++에서 특정 버전 이상에서만 지원하는 문법을 정리한다. C++ 버전별 주요 특징 Modern C++은 C++11 이후 버전을 의미한다. typedef typedef double my_type_t; == using my_type_t = double; (C++11 이상) enum enum { }; enum class { }; (C++11 이상) //c#의 enum과 같이 사용하려면 enum class를 사용한다. //enum은 스코프를 사용하지 않지만 enum class에서는 C#과 마찬가지로 스코프를 사용한다. string //c++의 string은 std 라이브러리에 포함된다. //cin으로 문자열을 온전히 받기 위해선 std::getline(std::cin, {변수명}) //이 사용된다. //cin으로 입력받은 버퍼를 비우기 위해 std::cin.ignore(std::numeric_limitsstd::streamsize::max(), '\n'); == std::cin.ignore(32767, '\n'); //사용이 필요하다. 코드에서 긴급한 탈출 (halt) HALT → exit(0); 랜덤 난수 생성 #include <cstdlib> std::srand(5323); //5323 = seed → 시드 넘버 지정 std::srand(static_cast<unsigned int>(std::time(0)); // 시간과 연동하여 시드가 계속 변경됨 std::rand(); #include <random> (C++11 이상) std::random_device rd; std::mt19937_64 mesenne(rd()); // or mt19937 std::uniform_int_distribution<> dice(1 ,6); // 1~6까지 같은 확률 cin 활용 # 버퍼 지우기 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); == std::cin.ignore(32767, '\n'); 입력 범위 문제 확인하기 (둘이 같이 사용됨.) std::cin.fail(); // return true / false std::cin.clear(); // 내부 상태 플래그 초기화 배열과 포인터 배열을 정의하면 배열의 이름 자체가 주소가 된다. 다만 배열을 함수의 인자로 넘겨, 매개변수로 배열을 받는다면 이 매개변수는 '포인터'로 취급된다. 때문에 매개변수로 받은 배열의 이름(포인터)은 배열의 주소를 저장하는 다른 주소가 된다. → 함수에서 이 (배열로 보이지만 포인터 변수인)배열의 sizeof를 찍으면 4바이트(32비트, 64비트에서는 8바이트)로 출력된다. 포인터를 그냥 선언해서는 변수값을 담을 수 없다.<blockquote> char *name = "nolda"; (X) 변수값을 담기 위해선 const 선언을 넣으면 사용할 수 있다. const char *name = "nolda"; (O) </blockquote> //참고 (*this).memberValue; == this->memberValue; foreach C#의 foreach와 사용법은 비슷하지만 문법의 차이가 존재한다. for (int value : array) { } 메모리 할당 정적으로 할당된 메모리는 stack에 저장되며, stack은 용량이 작고 컴파일 타임에 크기가 결정된다. 동적으로 할당된 메모리는 Heap에 할당된다. Heap 영역은 런타임에 결정된다. Stack / Heap Memory의 Segment 1. Code - Program 2. BSS - uninitialized data 3. Data - initialized data 4. Stack - 로컬 변수, 메인 함수, 메소드 등이 스택으로 쌓임 - 사이즈가 작기 때문에 Stack Overflow 발생 가능 5. Heap - 동적 할당일 경우 Heap 영역에 저장됨 const와 포인터 int value = 5; const int *ptr1 = &value; //ptr1에 저장돼있는 주소를 바꾸는건 가능하지만, 주소가 가리키는 value 값을 바꾸는건 안됨 int *const ptr2 = &value; //ptr2에 저장돼있는 주소 값 바꾸는게 안됨 const int *const ptr3 = &value; //주소 값도 바꿀 수 없고 de-referencing으로 값을 바꿀 수도 없음 (다 안됨) std::vector #include <vector> std::vector<int> array_value; 동적할당 배열에 유용하게 쓰이고 널리 쓰임. C#의 List와 비슷하다. //size : 사용하는 용량 -> .resize() //capacity : 총 용량(size에서 가려진 총 용량) -> .reserve //vector를 stack처럼 사용하기 .push_back();, .pop_back() std::tuple 여러 개의 반환 값을 return 시킬 수 있다. #include <tuple> std::tuple<int, double> getTuple() { return std::make_tuple(5, 3.14); } int main() { std::tuple<int, double> tp = getTuple(); std::get<0>(tp); //int 값 std::get<1>(tp); //double 값 //C++17 이상에서는 아래가 가능하다. auto[a, b] = getTuple(); } 함수포인터 int func() { return 5; } int func2() { return 9; } //함수포인터 변수 선언 int(*fcnptr)() = func; -> int(*변수이름)(매개변수) //다른 함수 할당 fcnptr = func2; //functional Library (C++11 이상) #include <functional> std::functional<리턴타입(매개변수)> fcnptr = func; 일립시스 (Ellipsis) //매개변수의 갯수제한을 두지 않고 받는 방법 double findAverage(int count, ...) //count : 매개변수 갯수 { double sum = 0; va_list list; var_start(list, count); for (int arg = 0; arg < count; ++arg) { sum += va_arg(list, int); } var_end(list); return sum / count; } 연쇄호출(Chaining) class 내부 함수의 리턴을 class 자신의 reference 값으로 설정하면, 연쇄적으로 호출이 가능하다. class Calc { int m_value; Calc& Add(int value) { m_value += value; return *this; } Calc& Sub(int value) { m_value -= value; return *this; } }; int main() { Calc cal(10); cal.Add(10).Sub(20).Add(10); } friends keyword class에서 다른 함수의 선언부를 가져와 friend 선언을 해주면 해당 함수에서는 선언된 class의 private 멤버에 접근할 수 있다. 각자 다른 class에서 공통 함수에 friend를 선언할 경우 전방선언이 필요할 수 있음. 순서에 의해 friend 함수에서 멤버 변수 등을 인식하지 못하면, class에는 선언부만 남겨두고, 인식하지 못한 class의 하단에 구현부를 넣어준다. 익명 변수 class A { void print() { cout << "A Print" << endl; } }; int main() { A().print(); A().print(); //위의 객체와 다르기 때문에 생성자와 소멸자가 각각 호출됨. } 연산자 오버로딩 //산술 연산자 class Cents { private: int m_cents; public: int& getCents() { return m_cents; } Cents operator + (const Cents &c2) //멤버 함수 { return Cents(this->m_cents + c2.m_cents); } friend Cents operator + (const Cents &c1, const Cents &c2) //friend 함수 { return Cents(c1.getCents() + c2.getCents()); } }; //입출력 연산자 class Point { private: double m_x, m_y, m_z; public: Point(double x = 0.0, double y = 0.0, double z = 0.0) : m_x(x), m_y(y), m_z(z) { } //outstream friend std::ostream& operator << (std::ostream &out, const Point &point) { out <<<< point.m_x << ", " << point.m_y << ", " << point.m_z; return out; } //instream friend std::istream& operator >> (std::istream& in, Point& point) { in >> point.m_x >> point.m_y >> point.m_z; return in; } }; int main() { Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0); Point p3, p4; cout << p1 << " " << p2 << endl; cin >> p3 >> p4; } //단항 연산자 Cents operator - () const { return Cents(-m_cents); } bool operator ! () const { return (m_cents == 0 ? true : false); } //비교 연산자 friend bool operator == (const Cents& c1, const Cents& c2) { return c1.m_cents == c2.m_cents; } friend bool operator < (const Cents& c1, const Cents& c2) { return c1.m_cents < c2.m_cents; } //증감 연산자 Cents& operator ++ () //prefix { ++m_cents; return *this; } Cents& operator ++ (int) //postfix { Cents temp(m_cents); ++(*this); return *temp; } //첨자 연산자 [] class IntList { private: int m_list[10]; public: int& operator [] (const int index) { return m_list[index]; } const int& operator [] (const int index) const { return m_list[index]; } }; int main() { IntList list; list[3] = 10; //if IntList *list = new IntList; list[3] = 10; //X (*list)[3] = 10; //O } //형변환 operator int() { return m_cents; } explicit / delete class Fraction { int m_numerator; int m_denominator; Fraction(char) = delete; //사용 못하게 막음 (explicit) Fraction(int num = 0, int den = 1) : m_numerator(num), m_denominator(den) { } }; void doSomething(Fraction frac) { cout << frac << endl; } int main() { doSomething(7); //원래는 컴파일러에서 Fraction(7)처럼 변환해줌 //만약 Fraction 생성자에 explicit 키워드가 앞에 붙는다면 불가능해짐 } class 깊은 복사 주의점 //class의 멤버변수로 char *m_data = nullptr;가 정의되어 있을 때, //이 class를 기본 복사 생성자를 통해 복사하면 새로운 class instance에도 같은 포인터 주소를 가리킨다. //이 때, 새로운 class instance가 삭제되면, 소멸자에서 해당 포인터 변수가 가리키는 값을 지워버리게 되고, //원본의 데이터까지 지워버리는 상황이 발생한다. (얕은 복사) //이 때문에, 깊은 복사를 위해선 별도의 복사 생성자를 정의해줘야 한다. MyString(const MyString &source) //복사 생성자 (깊은 복사) { m_length = source.m_length; if (source.m_data != nullptr) { m_data = new char[m_length]; for (int i = 0; i < m_length; ++i) m_data[i] = source.m_data[i]; } else m_data = nullptr; } } //operator = (대입 연산자) 의 경우에도 비슷하게 정의해줄 수 있음. MyString& operator = (const MyString &source) { if (this == &source) return *this; delete[] m_data; //기존에 갖고있던 메모리 할당 해제 m_length = source.m_length; if (source.m_data != nullptr) { m_data = new char[m_length]; for (int i = 0; i < m_length; ++i) m_data[i] = source.m_data[i]; } else m_data = nullptr; } } //std::string을 사용하면 필요 없는 일이다. Initializer List 생성자 IntArray(unsigned length) : m_length(length) { m_data = new int[length]; } IntArray(const std::initializer_list<int> &list) : IntArray(list.size()) { int count = 0; for (auto & element : list) { m_data[count] = element; ++count; } } 상속 관련 Keyword //virtual 상속 구조에서 부모 클래스의 메소드에 virtual 키워드를 사용시 자식 클래스의 객체를 부모 클래스의 포인터에 넣어서 호출해도 자식클래스의 메소드가 호출됨. 부모 클래스의 함수를 자식 클래스에서 오버라이딩한 것으로 인식 가상 소멸자 부모 클래스 객체에 자식 클래스를 넣을 경우, 자식 클래스의 동적 할당된 메모리를 지우기 위해 소멸자에도 virtual 키워드를 붙여주면 자식 클래스의 소멸자도 실행됨. //override 상속 구조에서 자식 클래스의 함수 매개변수 끝에 override 키워드 작성시 오버로딩이 아닌 오버라이드를 의도한것이라고 인식하게 하는 키워드 //final final 키워드 사용시 자식 클래스에서 더 이상 오버라이딩할 수 없음 다이아몬드 상속 문제 A라는 부모클래스를 통해 B와 C클래스를 상속받아 생성할 때, 상속 접근제한자에 virtual을 붙여주지 않으면 B와 C클래스 각각 다른 A클래스를 상속받는 문제가 발생할 수 있음. class B : virtual public A 와 같이 상속받아야 함. Object Slicing 부모 클래스로부터 상속받아 생성된 자식클래스에 새로운 변수가 있는데, 부모 클래스에 자식 클래스 인스턴스를 넣어버린 경우 데이터 슬라이싱 발생 std::vector를 사용하는 경우 std::vector<std::reference_wrapper<부모 클래스>> vec; 을 사용할 수 있다. dynamic cast Derived d1; Base *base = &d1; auto *base_to_d1 = dynamic_cast<Derived1*>(base); Base로 형변환 됐던 변수를 다시 Derived로 형변환 dynamic_cast의 경우 에러 체크를 통해 에러일 경우 nullptr 반환함. (안전한 형변환) static_cast는 에러 체크를 하지 않음. Template //함수 템플릿 template<typename T> T getMax(T x, T y) { return (x > y) ? x : y; } //클래스 템플릿 <*.h> template<typename T> class MyArray { private: int m_length; T *m_data; public: MyArray(int length) { m_lenth = length; m_data = new T [length]; } void print(); } ... <*.cpp> template<typename T> void MyArray<T>::print() { ... } template void MyArray<char>; //템플릿 클래스의 멤버함수의 구현부를 cpp 파일로 옮길 경우 explicit instantiation 필요 smart pointer: auto_ptr (Regacy) std::auto_ptr<int> //c++98 ~ c++11까지 존재 c++17부터 제거 //auto_ptr의 구조 template<typename T> class AutoPtr { public: AutoPtr(T* ptr = nullptr) : m_ptr(ptr) { } ~AutoPtr() { if (m_ptr != nullptr) delete m_ptr; } }; 이동(move semantics)의 의미 → auto_ptr의 변수는 복사하면 '이동'이 된다. A변수와 B변수가 존재할 때, B에 A를 대입하면 A는 nullptr을 갖게된다. auto_ptr은 배열에 사용할 경우 배열 이름 즉, 배열[0]요소에 대해서만 delete를 하는 치명적인 단점을 갖고있다. R-value Reference int x = 5; const int cx = 5; //int &&rr1 = x; (X) //int &&rr2 = cx; (X) int &&rr3 = 5; L Value Reference와 다르게 메모리가 할당되지 않은 값을 할당 가능 (곧 사라질 값들만 할당 가능) std::move AutoPtr<Resource> res2 = std::move(res1); //res1이 R-value임을 인식시켜줌 //이렇게 처리한 경우 res1을 사용하지 않는다는 의미 template<class T> void MySwap(T &a, T &b) { //Copy constructor T tmp = a; a = b; b = tmp; //Move Semantics T tmp { std::move(a) }; a = std::move(b); b = std::move(tmp); } //AutoPtr class에 Move Semantics를 정의했음 example) vector<string> v; string str = "Hello"; v.push_back(str); //L-value cout << std << endl; //Hello cout << v[0] << endl; //Hello v.push_back(std::move(str)); //R-value cout << str << endl; // 공백 cout << v[0] << " " << v[1] << endl; // Hello Hello std::unique_ptr / std::make_unique (주로 사용되는 smart pointer) #include <memory> std::unique_ptr<Resource> res(new Resource(10000)); 영역 밖을 벗어나 사용되지 않으면 자동으로 소멸됨 auto res1 = std::make_unique<Resource>(5); 권장되는 초기화 방식 res2 = res1; (x) res2 = std::move(res1); (O) unique pointer는 L-value 복사가 되지 않음 void doSomething(std::unique_ptr<Resource> res) { } doSomething(std::unique_ptr<Resource>(new Resource(1000))); (X) doSomething(std::make_unique<Resource>(1000)); (O) 위의 방식을 쓰면 컴파일러에 따라 문제가 발생할 수 있음 (parameter에서 new X) std::shared_ptr / std::make_shared std::shared_ptr<Resource> ptr1(res); ... { std::shared_ptr<Resource> ptr2(ptr1); std::shared_ptr<Resource> ptr2(res); //이렇게 사용하면 ptr1이 res의 소유권이 다른 데에도 있다는 것을 알 수가 없음 } ptr2의 블럭을 벗어나도, ptr1은 존재한다. auto ptr1 = std::make_shared<Resource>(3); std::weak_ptr class 내부에서 shared_ptr을 통해 서로를 참조시키면, memory leak이 남아있는 채로 종료됨 (순환 참조 문제) weak_ptr은 단독으로 사용할 수 없고, lock()을 해서 shared_ptr로 return해줘야 한다. const std::shared_ptr<Person> getPartner() const { return m_partner.lock(); //std::weak_ptr<Person> m_partner; } STL(Standard Template Library) CPP Reference https://cppreference.com/index.html Algorithms, Containers, Functions, Iterators를 포함 - std::set<T> //집합. 내부 원소가 겹치면 무시 set.insert("Hello"); set.insert("World"); set.insert("Hello"); > "Hello World" std::multiset<T> // 중복 원소 허용 집합 std::map<key, value> // key값에 sort 돼있음 .first // key 출력 .second // value 출력 std::multimap<key, value> // 중복 키값 허용 map multimap.insert(std::pair('a', 10)); //Before c++14, pair<char, int>('a', 10) std::stack .push // push adds a copy .emplace // constructs a new object std::queue std::priority_queue //sort 해주는 queue, 사용자 지정 클래스를 우선순위 큐로 만들면, 크기 비교 연산자 오버로딩을 해줘야함 STL Iterator (반복자) vector<int> container; for (int i = 0; i < 10; ++i) container.push_back(i); vector<int>::const_iterator itr; //vector<int>::iterator itr; itr = container.begin(); while(itr != container.end()) { cout << *itr << " "; ++ itr; } 어떤 컨테이너든 같은 코드로 순회가 가능하기 때문에 사용한다. → vector 를 list 또는 set 등으로 바꿔도 바로 동작함. for (auto itr = container.begin(); itr != container.end(); ++itr) cout << *itr << " "; for (auto &e : container) cout << e << " "; 모두 동일하게 동작한다. std::string / std::wstring wide-string : 글로벌 std::locale 사용시 주로 사용 (다양한 Unicode 지원) //int 값을 string으로 변환 → 문자열로 처리됨 std::string my_str(std::to_string(4)); //string을 int값으로 변환 int i = std::stoi(my_str); //std::ostringstream (output) / std::istringstream (input) string도 vector와 마찬가지로 길이와 용량이 다르다. C와 다르게, string의 뒤에는 null값 ('\0')이 포함되지 않는다. string.length() string.size() string.capacity() //용량 string.max_size() //최대 크기 string.reserve(1000) //용량 확보 (최소 용량) string my_str("abcdefg"); try { my_str[100] = 'X'; → 예외처리 X my_str.at(100) = 'X'; → 예외처리 O } catch { } my_str.c_str() == .data() → C 스타일로 사용할경우 마지막에 null값 ('\0') 포함 .append() → string의 끝에 문자열 붙이기 istream (Input Stream) #include <iomanip> char buf[10] cin >> setw(5) >> buf; //최대 5글자만 받도록 해줌. <iomanip> include 필요 setw()으로 글자수 제한을 하면, cin 버퍼에 남아있기 때문에 계속 존재함. cin이 빈칸을 구분자로 사용하지만, 빈칸까지 읽게하려면 cin.get(var) 을 사용 cin.get() 으로 읽은 후, cin.gcount() 를 통해 몇글자를 읽었는지 확인 가능 cin.getline() 은 라인 단위로 읽음. → 줄바꿈 문자까지 같이 읽어짐 ('\n') string buf; getline(cin, buf); cin.ignore() 는 한 글자를 무시한다. cin.peek() 은 버퍼에서 꺼내지 않고, 다음에 올 글자를 확인함 cin.unget() 은 마지막에 읽은 문자를 버퍼로 다시 넣음 cin.putback() 은 원하는 글자를 버퍼에 넣음 ostream (Output Stream) cout.setf(std::ios::showpos) 는 기호 (+, -)를 숫자 앞에 표시한다. cout.unsetf() 은 위의 플래그 삭제 cout.setf(std::ios::hex, std::ios::basefield) 는 16진수로 출력 == cout cout.setf(std::ios::uppercase) 는 16진수의 영문자를 대문자로 표시 cout << std::boolalpha 를 통해 bool 값 출력 cout << std::setprecision(3) 은 소숫점 자릿수 설정 cout << std::fixed 는 소숫점 자릿수 고정 cout << std::scientific 은 부동 소수점 방식 표기법 cout << std::showpoint 소수점 '.' 표기 cout << std::setw(10) << std::left(right / internal) << -12345 << endl; //출력 정렬 cout.fill("*") 빈 칸을 별로 채워줌 sstream (String Stream) #include <sstream> stringstream os; os << "Hello World"; // 버퍼에 덧붙임 os.str("Hello World"); //버퍼 치환 string str; str = os.str(); //os.str(""); 파라미터로 공백을 넣으면 치환됨 os >> str; cout << str; stream state void printStates(const std::ios& stream) { stream.good(); stream.eof(); stream.fail(); stream.bad(); } void printCharacterClassification(const int& i) { bool(std::isalnum(i)); bool(std::isblank(i)); bool(std::isdigit(i)); bool(std::islower(i)); bool(std::isupper(i)); // → return 값이 int이므로 bool로 캐스팅 } Regular Expressions (정규 표현식) #include <regex> // C++11 부터 지원 regex re("\d"); //digit 1개 == regex re("[[:digit:]]{1}"); regex re("\d+"); //1개 이상의 숫자 regex re("[ab]"); //a, b만 regex re("[A-Z]{1, 5}"); //1개 이상 5개 이하의 A-Z 문자 regex re("([0-9]{1})([-]?)([0-9]{1,4})"); // 0-9 숫자 1개 + '-'이 있어도 되고 없어도됨 + 0-9 숫자 1개이상 4개 이하 //regex_match를 통해 매치되는지 판별 string str; getline(cin, str); if (std::regex_match(str, re)) cout << "match" << endl; else cout << "No match" << endl; //매치되는 것만 출력 auto begin = std::sregex_iterator(str.begin(), str.end(), re); auto end = std::sregex_iterator(); for (auto itr = begin; itr != end; ++itr) { std::smatch match = *itr; cout << match.str() << " "; } cout << endl; fstream (File Stream - 파일 입출력) #include <fstream> //ASCII code - outputstream ofstream ofs("my_first_file.dat"); // ostream ofs("my_first_file.dat", ios::app) → append mode : 데이터를 추가 //ofs.open("my_first_file.dat"); ofs << "File Detail" << endl; //ofs.close() → 영역을 벗어나면 소멸자가 닫아줌. 수동으로 처리할 필요 X //ASCII code - inputstream ifstream ifs("my_first_file.dat"); while (ifs) { std::string str; getline(ifs, str); std::cout << str << endl; } //Binary code - outputstream const unsigned num_data = 10; ofs.write((char*)&num_data, sizeof(num_data)); //데이터 개수 정의 for (int i = 0; i < num_data; ++i) ofs.write((char*)&i, sizeof(i)); //Binary code - inputstream unsigned num_data = 0; ifs.read((char*)&num_data, sizeof(num_data)); //데이터 개수 확인 for (unsigned i = 0; i < num_data; ++i) { int num; ifs.read((char*)&num, sizeof(num)); std::cout << num << endl; } 임의 위치 접근 ifstream ifs("my_file.txt"); ifs.seekg(5); //5바이트 이동 후 읽기 시작 ifs.seekg(5, ios::cur); //이전에 이동했던 위치에서 5바이트 더 이동 후 읽기 시작 ifs.seekg(0, ios::end); //끝에서 0번째 (마지막 위치) ifs.tellg(); //현재 위치 파일 열고, 읽고 쓰기 한번에 fstream iofs(filename); iofs.seekg(5); cout iofs.seekg(5); iofs.put('A'); //write <hr/> Lambda (람다 함수) : 익명 함수 auto func = [](const int& i) -> void { cdout << "Hello, World!!" << endl; }; { string name = "JackJack"; <a href="">&</a> { std::cout << name << endl; } (); } //lambda의introducer인 []에 &을 넣으면, 밖에있는 것을 레퍼런스로 가져올 수 있음. == name <hr/> std::function std::function<void(int)> func3 = func2; std::function func4 = std::bind(func3, 456); //반환값 파라미터 bind Object instance; auto f = std::bind(&Object::hello, &instance, std::placeholders::_1); //멤버함수를 instance에 바인딩 //파라미터가 1개이므로 _1, 늘어나면 매개변수 추가(_2, _3, ...) <hr/> 함수에서 리턴값 여러개 반환하기 (C++17) #include auto my_func() { return tuple(123, 456, 789); } int main() { auto [a, b, c, d] = my_func(); std::cout << a << " " << b << " " << c << " " << d << endl; return 0; } <hr/> std::thread - 멀티 스레딩 (C++11) std::thread t1 = std::thread( { while (true) { } }); t1.join(); //thread가 있는데, main이 끝나버릴 수 있으므로 t1이 끝날 때까지 대기해줌 - 스레드가 여러개 있다면, 동시에 실행된다. mutex mtx; //mutual exclusion (상호 배제) auto work_func = [](const string& name) { for (int i = 0; i mtx.lock(); cout << name << " " << std::this_thread::get_id() << " is working " << i << endl; mtx.unlock(); } }; std::thread t1 = std::thread(work_func, "JackJack"); std::thread t2 = std::thread(work_func, "Dash"); t1.join(); t2.join(); <hr/> Race Condition - std::atomic, std::scoped_loc #include //int shared_memory(0); → t1 스레드가 더하는 순간 t2가 가로채는 문제 발생할 수 있음. atomic shared_memory(0); //문제 해결 int main() { auto count_func = { for (int i = 0; i thread t1 = thread(count_func); thread t2 = thread(count_func); t1.join(); t2.join(); cout << "After" << endl; cout << shared_memory << endl; } - 다른 스레드가 메모리를 가로채는 문제 - std::atomic뿐만 아니라 mutex를 사용할 수도 있음. - atomic 남용시 성능 하락 우려 존재 ... std::lock_guard lock(mtx); shared_memory++; ... - lock 후 unlock을 할 수 없을 때도 있으므로, `std::lock_guard` 사용을 권장 ... std::scoped_lock lock(mtx); ... - (C++17 이상) `std::scoped_lock` 권장 <hr/> 작업 기반 비동기 프로그래밍 (Task) #include int main() { { //multi-threading int result; std::thread t([&] {result = 1 + 2;} ); t.join(); std::cout { //task-based parallelism auto fut = std::async([] {return 1 + 2;}); std::cout << fut.get() << std::endl; } { //future and promise std::promise<int> prom; auto fut = prom.get_future(); auto t = std::thread([](std::promise<int>&& prom) { prom.set_value(1 + 2); }, std::move(prom)); cout << fut.get() << endl; t.join(); } } - async는 join()으로 기다리지 않아도 된다 <hr/> std::forward - 완벽한 전달 #include struct MyStruct {}; void func(MyStruct& s) { cout void func(MyStruct&& s) { cout //Template을 사용하면 L-value와 R-value 구분을 못한다. template void func_wrapper(T t) { func(t); } ↓ Perfect Forwarding template void func_wrapper(T&& t) { func(std::forward (t)); } int main() { MyStruct s; func_wrapper(s); func_wrapper(MyStruct()); //func(s); //func(MyStruct()); } <hr/> 자료형 추론 - `auto` / `template<typename>` - auto는 변수의 const와 &, volatile를 모두 떼버린다. - `const auto& auto_crx2 = crx`, `volatile auto vavx = vs` 와 같이 선언해야 한다. - `decltype` (== typeof) typedef decltype(lhs ** rhs) product_type; product_type prod2 = lhs * rhs; == decltype(lhs * rhs) prod3 = lhs * rhs; typedef decltype(x) x_type; typedef decltype((x)) x_type; → &를 붙여줌 ```