Modern C++ Summary (vs. C#)

w99hyun·2025년 9월 9일


개인적으로 공부하면서, 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_limits<std::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바이트)로 출력된다. 

- 포인터를 그냥 선언해서는 변수값을 담을 수 없다.
> char *name = "nolda"; (X)
변수값을 담기 위해선 const 선언을 넣으면 사용할 수 있다.
> const char *name = "nolda"; (O)

//참고
(*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)

- 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;
}
  • 어떤 컨테이너든 같은 코드로 순회가 가능하기 때문에 사용한다. → vectorlist 또는 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 << std::hex;
  • 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 << (char)iofs.get() << endl; //read

iofs.seekg(5);
iofs.put('A'); //write

Lambda (람다 함수) : 익명 함수

auto func = [](const int& i) -> void { cdout << "Hello, World!!" << endl; };
{
	string name = "JackJack";
    [&]() { std::cout << name << endl; } ();
}

//lambda의introducer인 []에 &을 넣으면, 밖에있는 것을 레퍼런스로 가져올 수 있음. == name

std::function

std::function<void(int)> func3 = func2;

std::function<void()> func4 = std::bind(func3, 456); //반환값 파라미터 bind
Object instance;
auto f = std::bind(&Object::hello, &instance, std::placeholders::_1);
//멤버함수를 instance에 바인딩
//파라미터가 1개이므로 _1, 늘어나면 매개변수 추가(_2, _3, ...)

함수에서 리턴값 여러개 반환하기 (C++17)

#include <tuple>

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;
}

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 < 5; ++i)
    {
    	std::this_thread::sleep_for(std::chrono::milliseconds(100));
        
        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();

Race Condition - std::atomic, std::scoped_loc

#include <atomic>

//int shared_memory(0);
→ t1 스레드가 더하는 순간 t2가 가로채는 문제 발생할 수 있음.
atomic<int> shared_memory(0); //문제 해결

int main()
{
	auto count_func = []() {
    	for (int i = 0; i < 1000; ++i)
        {
        	this_thread::sleep_for(chrono::milliseconds(1));
            shared_memory++;
            // == shared_memory.fetch_add(1);
        }
    };
    
    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 권장

작업 기반 비동기 프로그래밍 (Task)

#include <future>

int main()
{
	{ //multi-threading
		int result;
      	std::thread t([&] {result = 1 + 2;} );
      	t.join();
      	std::cout << result << std::endl;
    }
    
    { //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()으로 기다리지 않아도 된다

std::forward - 완벽한 전달

#include <utility>

struct MyStruct
{};

void func(MyStruct& s)
{
	cout << "Pass by L-ref" << endl;
}

void func(MyStruct&& s)
{
	cout << "Pass by R-ref" << endl;
}

//Template을 사용하면 L-value와 R-value 구분을 못한다.
template<typename T>
void func_wrapper(T t)
{
	func(t);
}

↓ Perfect Forwarding

template<typename T>
void func_wrapper(T&& t)
{
	func(std::forward<T>(t));
}

int main()
{
	MyStruct s;
    
    func_wrapper(s);
    func_wrapper(MyStruct());
    
    //func(s);
    //func(MyStruct());
}

자료형 추론

  • 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; → &를 붙여줌

0개의 댓글