본문 바로가기

개발 공부/C++

따라하며 배우는 C++ 19. 모던 C++ 필수 요소들

따라하며 배우는 C++ 19. 모던 C++ 필수 요소들

 

19.1 람다 함수와 std::function, std::bind, for_each

 

 

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

void goodbye(const string& s)
{
	cout << "Goodbye " << s << endl;
}
class Object
{
public:
	void hello(const string& s)
	{
		cout << "Hello " << s << endl;
	}
};

int main()
{
	//lambda-introducer
	//lambda-parameter-declaration
	//lambda-return-type-clause
	//compound-statement
	auto func = [](const int& i)->void {cout << "Hello, World" << endl; };
	//[]를 lambda-introducer라고 부름
	//일반적인 함수처럼 parameter를 정의할 수 있고,
	//return type을 ->를 써서 뒤에 표기함.

	func(123);

	[](const int& i) -> void { cout << "Hello, World!" << endl; }(1234);
	//잡다하게 함수 구현할 경우, 람다 함수를 쓰면 간편해진다.
	//특히 GUI일 경우

	{
		string name = "JackJack";
		[&]() {std::cout << name << endl; }();
		//블록 안에서 &를 쓰면 블록 안의 변수를 가져온다.
		//[&name] 도 가능.
		//[this]하면 클래스의 멤버도 가능
		//[=]라고 하면 변수의 값을 복사해서 대입한다.
		//scope의 변수를 싹 가져다 쓸 수 있다!
	}
	vector<int> v;
	v.push_back(1);
	v.push_back(2);

	auto func2 = [](int val) {cout << val << endl; };
	for_each(v.begin(), v.end(), func);
	
	//for_each(v.begin(), v.end(), [](int val) {cout << val << endl; });
	//사실 이렇게 바로 넣어버리는 것을 선호하긴 한다.

	cout << []()->int {return 1; }() << endl;

	std::function<void(int)> func3 = func2;
	//함수 포인터를 체계화 시켜준 것. <return type(parameter type)>
	func3(123);

	std::function<void()>func4 = std::bind(func3, 456);
	func4(); //parameter에 456을 알아서 넣어준다.

	{
		Object instance;
		auto f = std::bind(&Object::hello, &instance, std::placeholders::_1);

		//parameter가 여러 개 있을 때, palceholder를 쓸 수 있다.
		//object 내의 함수를 쓰려면 클래스의 인스턴스가 필요하다.
		//함수의 포인터, 인스턴스 포인터, 파라미터 플래그를 넣어줌.
		//멤버 function을 instance에 bind

		f(string("World"));

		auto f2 = std::bind(&goodbye, std::placeholders::_1);
		f2(string("World"));
	}

}

 

 

 

 

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

 

프로젝트 이름 > 속성 > C/C++ > 언어 > C++언어 표준 > 설정

 

 

#include <iostream>
#include <tuple>
using namespace std;

tuple<int, int> my_func()
{
	return tuple<int, int>(123, 456);
}

int main()
{
	cout << "Hello, World" << endl;
	tuple<int, int> result = my_func();
	cout << get<0>(result) << " " << get<1>(result) << endl;
	return 0;
}

예전에 return 값 여러 개를 받기 위해 tuple을 써서 우회하는 방법.

 

#include <iostream>
#include <tuple>
using namespace std;

auto my_func()
{
	return tuple<int, int>(123, 456);
}

int main()
{
	cout << "Hello, World" << endl;
	auto result = my_func();
	cout << get<0>(result) << " " << get<1>(result) << endl;
	return 0;
}

 

 

//위의 방법도 가능

 

아래는 17에서 더 편리해진 방법

 

#include <iostream>
#include <tuple>
using namespace std;

auto my_func()
{
	return tuple(123, 456, 789);
}

int main()
{
	cout << "Hello, World" << endl;
	auto [a, b, c] = my_func();
	//변수 a b c d를 선언하면서 받는 형태가 될 수 있다.
	cout << a << " " << b << " " << c << endl;
	return 0;
}

 

 

 

19.3 std thread와 멀티쓰레딩multithreading 기초

 

프로세스란, OS가 프로그램을 실행시킬 때 관리하는 단위.

하나의 프로세스가 여러 개의 thread를 관리할 수 있다.

여러 개의 core를 동시에 가동하여 효율성을 높이는 방법.

 

코어가 하나였을 때는 프로세스 하나에 CPU를 여러 개 꽂는 게 더 속도가 안 좋았다.

그래서 멀티쓰레딩 대신 멀티 프로세싱을 하기도 했다.

네트워크를 통해 여러 대의 컴퓨터로 분산 처리를 시도하기도 했다.

분산 처리는 메모리를 공유할 수 없고, 통신을 할 때 부가적인 오버헤드도 있어 비효율적인 면도 있다.

 

반면에 멀티쓰레드는 메모리를 공유해서 프로그래머를 편하게 만들어주기도 하는 반면,

위험하게 만들기도 한다.

 

(작업 관리자 창)

보통 물리적 코어의 두 배를 프로세서로 본다.

utilization이 현재 CPU가 가동되는 비율.

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
using namespace std;

int main()
{
	const int num_pro = std::thread::hardware_concurrency();
	cout << num_pro << endl;
	//CPU 코어 개수
	cout << std::this_thread::get_id() << endl;
	//메인 함수가 실행되고 있는 쓰레드의 아이디.

	std::thread t1 = std::thread(
		[]()
		{
			while (true) {}
		}
	);
	return 0;
}

 

쓰레드가 분기되었는데, 메인 쓰레드가 먼저 끝나서 에러가 생긴 것.

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
using namespace std;

int main()
{
	const int num_pro = std::thread::hardware_concurrency();
	cout << num_pro << endl;
	//CPU 코어 개수
	cout << std::this_thread::get_id() << endl;
	//메인 함수가 실행되고 있는 쓰레드의 아이디.

	std::thread t1 = std::thread(
		[]()
		{
			cout << std::this_thread::get_id() << endl;
			while (true) {}
		}
	);
	t1.join(); //t1이 끝날 때까지 기다려준다.
	return 0;
}

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
using namespace std;

int main()
{
	const int num_pro = std::thread::hardware_concurrency();
	cout << num_pro << endl;
	//CPU 코어 개수
	cout << std::this_thread::get_id() << endl;
	//메인 함수가 실행되고 있는 쓰레드의 아이디.

	std::thread t1 = std::thread(
		[]()
		{
			cout << std::this_thread::get_id() << endl;
			while (true) {}
		}
	);

	std::thread t2 = std::thread(
		[]()
		{
			cout << std::this_thread::get_id() << endl;
			while (true) {}
		}
	);
	std::thread t3 = std::thread(
		[]()
		{
			cout << std::this_thread::get_id() << endl;
			while (true) {}
		}
	);
	std::thread t4 = std::thread(
		[]()
		{
			cout << std::this_thread::get_id() << endl;
			while (true) {}
		}
	);
	t1.join();
	t2.join();
	t3.join();
	t4.join();
	return 0;
}

이렇게 여러 개의 쓰레드를 돌릴 수 있다.

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
using namespace std;

int main()
{
	const int num_pro = std::thread::hardware_concurrency();
	cout << num_pro << endl;
	//CPU 코어 개수
	cout << std::this_thread::get_id() << endl;
	//메인 함수가 실행되고 있는 쓰레드의 아이디.

	vector<std::thread> my_threads;
	my_threads.resize(num_pro);

	for (auto& e : my_threads)
		e = std::thread([]() {
		cout << std::this_thread::get_id() << endl;
		while (true) {}});

	for (auto& e : my_threads)
		e.join();

	return 0;
}

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
using namespace std;

int main()
{
	auto work_func = [](const string& name)
	{
		for (int i = 0; i < 5; ++i)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
			cout << name << " " << std::this_thread::get_id() << " is working " << i << endl;
		}
	};

	work_func("JackJack");
	work_func("Dash");

	return 0;
}

 

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
using namespace std;

int main()
{
	auto work_func = [](const string& name)
	{
		for (int i = 0; i < 5; ++i)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
			cout << name << " " << std::this_thread::get_id() << " is working " << i << endl;
		}
	};

	std::thread t1 = std::thread(work_func, "JackJack");
	std::thread t2 = std::thread(work_func, "Dash");

	t1.join();
	t2.join();

	return 0;
}

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
using namespace std;
mutex mtx; //mutual exclution의 약자
//독점할 권리를 선언할 수 있다.

int main()
{
	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();

	return 0;
}

lock을 해 주면 꼭 unlock을 해 주어야 한다.

멀티쓰레딩에서는 기능이 문제가 아니라, 중복되는 기능을 한번에 쓸 때 어떻게 대처해야 하는가가 더 중요하다.

 

 

19.4 레이스 컨디션Race condition, std::atomic, std::scoped_lock

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
#include <atomic>
using namespace std;


int main()
{
	int shared_memory(0);
	auto count_func = [&]() {
		for (int i = 0; i < 1000; ++i)
		{
			this_thread::sleep_for(chrono::milliseconds(1));
			shared_memory++;
		}
	};
	thread t1 = thread(count_func);
	t1.join();

	cout << "After" << endl;
	cout << shared_memory << endl;
	return 0;
}

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
#include <atomic>
using namespace std;


int main()
{
	int shared_memory(0);
	auto count_func = [&]() {
		for (int i = 0; i < 1000; ++i)
		{
			this_thread::sleep_for(chrono::milliseconds(1));
			shared_memory++;
		}
	};
	thread t1 = thread(count_func);
	thread t2 = thread(count_func);
	t1.join();
	t2.join();

	cout << "After" << endl;
	cout << shared_memory << endl;
	return 0;
}

2000이 나와야 하는데, 이상한 숫자가 나온다.

thread1이 shared_memory에 값을 메모리에서 가져와 CPU에서 1을 더해준 다음,

더한 값을 원래 있었던 메모리에 덮어쓴다.

 

thread1이 shared_memory을 읽어들인 사이에, thread2가 같은 메모리를 똑같이 가져와

덮어쓴다면, 값이 원하는 데로 증가하지 않는다.

 

<atomic>은 operation이 메모리에서 읽어 오고, 작업을 하고, 다시 저장을 하는

일련의 3단계 작업을 한번에 수행하도록 묶어 버린다.

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
#include <atomic>
using namespace std;


int main()
{
	atomic<int> shared_memory(0);
	auto count_func = [&]() {
		for (int i = 0; i < 1000; ++i)
		{
			this_thread::sleep_for(chrono::milliseconds(1));
			shared_memory++;
		}
	};
	thread t1 = thread(count_func);
	thread t2 = thread(count_func);
	t1.join();
	t2.join();

	cout << "After" << endl;
	cout << shared_memory << endl;
	return 0;
}

 

shared_memory.fetch_add(1); //이것으로 사용해도 된다.

그냥 integer를 사용할 때보다 더 느려질 수도 있다.

 

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
#include <atomic>
using namespace std;

mutex mtx;
int main()
{
	int shared_memory(0);
	auto count_func = [&]() {
		for (int i = 0; i < 1000; ++i)
		{
			this_thread::sleep_for(chrono::milliseconds(1));
			//mtx.lock()
			std::lock_guard lock(mtx);
			//block안에서 이렇게 선언하여 블록이 끝나면 알아서 unlock을 해 준다.
			shared_memory++;
			//mtx.unlock();

		}
	};
	thread t1 = thread(count_func);
	thread t2 = thread(count_func);
	t1.join();
	t2.join();

	cout << "After" << endl;
	cout << shared_memory << endl;
	return 0;
}

C++17 부터는 std::scoped_lock lock(mtx);이 권장된다.

병렬 처리는 특정 수행이 빠르게 처리될 경우, 오류를 찾기가 힘들다.

 

 

19.5 작업 기반 비동기 프로그래밍 aync, future, promise

 

 

#include <iostream>
#include <future>
##include <thread>
using namespace std;

int main()
{
	//multi-threading
	{
		int result;
		std::thread t([&] {result = 1 + 2; });
		t.join();
		cout << result << endl;
	}
	//일반적으론 scope를 잡고, 그 scope의 변수를
	//여러 개의 thread들이 공유하는 것이 일반적이다.
	//thread 위주로 프로그래밍이 진행된다.

	//task-based parallelism
	{
		//std::future<int> fut = ....
		auto fut = std::async([] { return 1 + 2; });
		//main thread가 작업하는 것 이외에도 해당 함수 내의 작업을
		//멀티쓰레드처럼 동시에 진행한다.
		//그 결과를 fut에 받음.
		cout << fut.get() << endl;
	}
	//task-based를 thread-based보다 더 선호한다.
	//thread-based는 thread를 생성하고, thread에 어떻게 작업을 분배할 것인가에
	//더 중점을 둔다면
	//task-based는 어떤 작업을 할 것인가를 위주로 프로그래밍한다.
	//return 값을 현재에 바로 받을 수 있다는 보장이 있을 때 쓰인다.
	//thread에선 결과값을 받아오는 변수를 thead 밖에서도 접근할 수 있지만
	//task-based에선 바로 결과값을 받아오기 때문에 해당 변수가 task와 더욱 밀접하다.
	//fut.get()은 fut의 결과값이 있을 때까지 기다릴 수 있다.
	//t.join()은 반대로 thread가 끝날 때까지 기다린다!
}

 

#include <iostream>
#include <future>
##include <thread>
using namespace std;

int main()
{
	//future and promise
	{
		std::promise<int> prom;
		auto fut = prom.get_future();
		//여기서 아까 예제에선 async로부터 직접 future를 갖고 왔다면
		//thread는 결과 값이 아닌 자기 thread를 return하므로,
		//future를 받을 수 있는 다른 존재 하나가 필요하기 때문에
		//중간에 promise를 거쳐 가는 것이다.

		// 이 때 promise에 어떤 값을 넣어주면, future가 발동된다.

		auto t = std::thread([](std::promise<int>&& prom)
			//promise를 parameter로 넣어줘야 한다.
			{
				prom.set_value(1 + 2); //promise에 결과 값을 넣어준다.
			}, std::move(prom));
		//promise가 완수되기를 하염없이 기다린다.
		cout << fut.get() << endl; //promise에 넣어준 값이 나온다.
		t.join(); //thread는 thread이므로 join 해야 함.
	}
}
#include <iostream>
#include <future>
##include <thread>
using namespace std;

int main()
{
	//future and promise
	{
		std::promise<int> prom;
		auto fut = prom.get_future();
		//여기서 아까 예제에선 async로부터 직접 future를 갖고 왔다면
		//thread는 결과 값이 아닌 자기 thread를 return하므로,
		//future를 받을 수 있는 다른 존재 하나가 필요하기 때문에
		//중간에 promise를 거쳐 가는 것이다.

		// 이 때 promise에 어떤 값을 넣어주면, future가 발동된다.

		auto t = std::async([](std::promise<int>&& prom)
			//promise를 parameter로 넣어줘야 한다.
			{
				prom.set_value(1 + 2); //promise에 결과 값을 넣어준다.
			}, std::move(prom));
		//promise가 완수되기를 하염없이 기다린다.
		cout << fut.get() << endl; //promise에 넣어준 값이 나온다.
		
	}
}

async로도 넣을 수 있다.

 

#include <iostream>
#include <future>
#include <thread>
using namespace std;

int main()
{
	{
		auto f1 = std::async([] {
			cout << "async1 start" << endl;
			this_thread::sleep_for(chrono::seconds(2));
			cout << "async1 end" << endl;
			});

		auto f2 = std::async([] {
			cout << "async2 start" << endl;
			this_thread::sleep_for(chrono::seconds(2));
			cout << "async2 end" << endl;
			});

		cout << "Main function" << endl;
		//async는 join이 없어도 문제가 생기지 않는다.
	}
}

async 예제.

thread와는 내부적으로 처리하는 방식이 좀 다르다.

async는 받아주는 promise가 없을 경우 그냥 sequential로 구동된다.

 

 

19.6 멀티쓰레딩 예제(벡터 내적)

 

Naive한 코드는 다음과 같다.

	void dotProductNaive(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, unsigned long& sum)
{
	for (unsigned i = i_start; i < i_end; ++i)
		sum += v0[i] * v1[i];
}
    
    
    //cout << "Naive" <<endl;
	{
		const auto sta = chrono::steady_clock::now();
		unsigned long long sum = 0;
		vector<thread> threads;	//쓰레드의 벡터
		threads.resize(n_threads);

		const unsigned n_per_thread = n_data / n_threads; //assumes remainder=0
		//thread 생성 중
		for (unsigned t = 0; t < n_threads; ++t)
			threads[t] = std::thread(dotProductNaive, std::ref(v0), std::ref(v1),
				t * n_per_thread, (t + 1) * n_per_thread, std::ref(sum));

		for (unsigned t = 0; t < n_threads; ++t)
			threads[t].join();

		const chrono::duration<double> dur = chrono::steady_clock::now() - sta;
		cout << dur.count() << endl;
		cout << sum << endl;
		cout << endl;
	}

Race condition 때문에 제대로 작동하지 않는다.

 

#include <iostream>
#include <vector>
#include <mutex>
#include <random>
#include <utility>
#include <thread>
#include <atomic>
#include <future>
#include <numeric> // std::innter_product
#include <execution> // parrallen execution

using namespace std;
mutex mtx;
void dotProductNaive(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, unsigned long& sum)
{
	for (unsigned i = i_start; i < i_end; ++i)
		sum += v0[i] * v1[i];
}

void dotProductLock(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, unsigned long long& sum)
{
	//cout << "Thread start" << endl;
	for (unsigned i = i_start; i < i_end; ++i)
	{
		std::scoped_lock lock(mtx);
		sum += (v0[i] * v1[i]);
	}
}

int main()
{
	/*
	v0 = {1, 2, 3}
	v1 = {4, 5, 6}
	v0_dot_v1 = 1*4 + 2*5 + 3*6; 
	*/
	const long long n_data = 100'000'000;
	const unsigned n_threads = 4;

	//initialize vectors
	std::vector<int> v0, v1;
	v0.reserve(n_data);
	v1.reserve(n_data);

	random_device seed;
	mt19937 engine(seed());

	uniform_int_distribution<> uniformDist(1, 10);

	for (long long i = 0; i < n_data; ++i)
	{
		v0.push_back(uniformDist(engine));
		v1.push_back(uniformDist(engine));
	}

	cout << "std::inner_product" << endl;
	{
		const auto sta = chrono::steady_clock::now();
		const auto sum = std::inner_product(v0.begin(), v0.end(), v1.begin(), 0ull);
		//0ull --> unsigned long long
		const chrono::duration<double> dur = chrono::steady_clock::now() - sta;

		cout << dur.count() << endl; //계산된 시간
		cout << sum << endl;
		cout << endl;
	}// 정답도 출력, 퍼포먼스도 출력


	{
		const auto sta = chrono::steady_clock::now();
		unsigned long long sum = 0;
		vector<thread> threads;
		threads.resize(n_threads);
		const unsigned n_per_thread = n_data / n_threads;
		for (unsigned t = 0; t < n_threads; ++t)
			threads[t] = std::thread(dotProductLock, std::ref(v0), std::ref(v1),
				t * n_per_thread, (t + 1) * (n_per_thread), std::ref(sum));

		for (unsigned t = 0; t < n_threads; ++t)
			threads[t].join();

		const chrono::duration <double> dur = chrono::steady_clock::now() - sta;

		cout << dur.count() << endl;
		cout << sum << endl;
		cout << endl;
	}
	return 0;
}

 

Race condition이 방지되지만, 느리다.

 

함수 전체 범위에 lock을 걸면 concurrent하게 실행되는 게 아니라, sequential하게 실행되므로 병렬 처리가 의미 없어진다.

scope_lock은 작은 범위의 블록에 거는 것이 좋지만, 빈번하게 호출되면 퍼포먼스가 오히려 더 떨어질 수도 있다.

 

#include <iostream>
#include <vector>
#include <mutex>
#include <random>
#include <utility>
#include <thread>
#include <atomic>
#include <future>
#include <numeric> // std::innter_product
#include <execution> // parrallen execution

using namespace std;
mutex mtx;
void dotProductNaive(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, unsigned long& sum)
{
	for (unsigned i = i_start; i < i_end; ++i)
		sum += v0[i] * v1[i];
}

void dotProductLock(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, unsigned long long& sum)
{
	//cout << "Thread start" << endl;
	for (unsigned i = i_start; i < i_end; ++i)
	{
		std::scoped_lock lock(mtx);
		sum += (v0[i] * v1[i]);
	}
}

void dotProductAtomic(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, atomic<unsigned long long>& sum)
{
	for(unsigned i = i_start; i < i_end; ++i)
	{
		sum += v0[i] * v1[i];
	}
}

int main()
{
	/*
	v0 = {1, 2, 3}
	v1 = {4, 5, 6}
	v0_dot_v1 = 1*4 + 2*5 + 3*6; 
	*/
	const long long n_data = 100'000'000;
	const unsigned n_threads = 4;

	//initialize vectors
	std::vector<int> v0, v1;
	v0.reserve(n_data);
	v1.reserve(n_data);

	random_device seed;
	mt19937 engine(seed());

	uniform_int_distribution<> uniformDist(1, 10);

	for (long long i = 0; i < n_data; ++i)
	{
		v0.push_back(uniformDist(engine));
		v1.push_back(uniformDist(engine));
	}

	cout << "std::inner_product" << endl;
	{
		const auto sta = chrono::steady_clock::now();
		const auto sum = std::inner_product(v0.begin(), v0.end(), v1.begin(), 0ull);
		//0ull --> unsigned long long
		const chrono::duration<double> dur = chrono::steady_clock::now() - sta;

		cout << dur.count() << endl; //계산된 시간
		cout << sum << endl;
		cout << endl;
	}// 정답도 출력, 퍼포먼스도 출력


	//atomic
	cout << "Atomic" << endl;
	{
		const auto sta = chrono::steady_clock::now();

		atomic<unsigned long long> sum = 0;

		vector<thread> threads;
		threads.resize(n_threads);

		const unsigned n_per_thread = n_data / n_threads;
		for (unsigned t = 0; t < n_threads; ++t)
			threads[5] = std::thread(dotProductAtomic, std::ref(v0), std::ref(v1),
				t * n_per_thread, (t + 1) * n_per_thread, std::ref(sum));

		for (unsigned t = 0; t < n_threads; ++t)
			threads[t].join();

		const chrono::duration<double> dur = chrono::steady_clock::now() - sta;

		cout << dur.count() << endl;
		cout << sum << endl;
		cout << endl;
	}

	return 0;
}

Atomic 예제

 

 

#include <iostream>
#include <vector>
#include <mutex>
#include <random>
#include <utility>
#include <thread>
#include <atomic>
#include <future>
#include <numeric> // std::innter_product
#include <execution> // parrallen execution

using namespace std;
mutex mtx;
void dotProductNaive(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, unsigned long& sum)
{
	for (unsigned i = i_start; i < i_end; ++i)
		sum += v0[i] * v1[i];
}

void dotProductLock(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, unsigned long long& sum)
{
	//cout << "Thread start" << endl;
	for (unsigned i = i_start; i < i_end; ++i)
	{
		std::scoped_lock lock(mtx);
		sum += (v0[i] * v1[i]);
	}
}

void dotProductAtomic(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end, atomic<unsigned long long>& sum)
{
	for(unsigned i = i_start; i < i_end; ++i)
	{
		sum += v0[i] * v1[i];
	}
}

auto dotProductFuture(const vector<int>& v0, const vector<int>& v1,
	const unsigned i_start, const unsigned i_end)
{
	int sum = 0;
	//local sum
	for (unsigned i = i_start; i < i_end; ++i)
		sum += v0[i] * v1[i];

	return sum;
}

int main()
{
	/*
	v0 = {1, 2, 3}
	v1 = {4, 5, 6}
	v0_dot_v1 = 1*4 + 2*5 + 3*6; 
	*/
	const long long n_data = 100'000;
	const unsigned n_threads = 4;

	//initialize vectors
	std::vector<int> v0, v1;
	v0.reserve(n_data);
	v1.reserve(n_data);

	random_device seed;
	mt19937 engine(seed());

	uniform_int_distribution<> uniformDist(1, 10);

	for (long long i = 0; i < n_data; ++i)
	{
		v0.push_back(uniformDist(engine));
		v1.push_back(uniformDist(engine));
	}

	cout << "std::inner_product" << endl;
	{
		const auto sta = chrono::steady_clock::now();
		const auto sum = std::inner_product(v0.begin(), v0.end(), v1.begin(), 0ull);
		//0ull --> unsigned long long
		const chrono::duration<double> dur = chrono::steady_clock::now() - sta;

		cout << dur.count() << endl; //계산된 시간
		cout << sum << endl;
		cout << endl;
	}// 정답도 출력, 퍼포먼스도 출력


	cout << "Future" << endl;
	{
		const auto sta = chrono::steady_clock::now();
		unsigned long long sum = 0;
		vector<std::future<int>> futures;

		futures.resize(n_threads);

		const unsigned n_per_thread = n_data / n_threads;
		for (unsigned t = 0; t < n_threads; ++t)
			futures[t] = std::async(dotProductFuture, std::ref(v0), std::ref(v1),
				t * n_per_thread, (t + 1) * n_per_thread);
		for (unsigned t = 0; t < n_threads; ++t)
			sum += futures[t].get();

		const chrono::duration<double> dur = chrono::steady_clock::now() - sta;
		cout << dur.count() << endl;
		cout << sum << endl;
		cout << endl;
	}

	return 0;
}

 

task-based

 

 

 

19.7 완벽한 전달Perfect Forwarding과 std::forward

 

#include <iostream>
#include <vector>
#include <utility> //std::forward
using namespace std;
struct MyStruct
{};
void func(MyStruct& s)
{
	cout << "Pass by L-ref" << endl;
}

void func(MyStruct&& s) //오버로딩
{
	cout << "Pass by R-ref" << endl;
}
int main()
{
	MyStruct(s);
	func(s); //l-value
	func(MyStruct()); //r-value
}

 

IDE 자체적으로 L-ref, R-ref를 구분해준다!

 

#include <iostream>
#include <vector>
#include <utility> //std::forward
using namespace std;
struct MyStruct
{};
void func(MyStruct& s)
{
	cout << "Pass by L-ref" << endl;
}

void func(MyStruct&& s) //오버로딩
{
	cout << "Pass by R-ref" << endl;
}

template<typename T>
void func_wrapper(T t)
{
	func(t);
}

int main()
{
	MyStruct(s);
	func_wrapper(s);
	func_wrapper(MyStruct());
	return 0;
}

 

그러나 templatize를 하면 L-value인지, R-value인지 구분할 수 있는 정보가 날라가 버린다.

 

#include <iostream>
#include <vector>
#include <utility> //std::forward
using namespace std;
struct MyStruct
{};
void func(MyStruct& s)
{
	cout << "Pass by L-ref" << endl;
}

void func(MyStruct&& s) //오버로딩
{
	cout << "Pass by R-ref" << endl;
}

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

int main()
{
	MyStruct(s);
	func_wrapper(s);
	func_wrapper(MyStruct());
	return 0;
}

perfect-forward를 쓰면 위 두개를 구분할 수 있게 된다.

#include <iostream>
#include <vector>
#include <cstdio>
#include <utility>

using namespace std;

class CustomVector
{
public:
	unsigned n_data = 0;
	int* ptr = nullptr;

	CustomVector(const unsigned& _n_data, const int& _init = 0)
	{
		cout << "Constructor" << endl;
		init(_n_data, _init);
	}

	CustomVector(CustomVector& l_input)
	{
		cout << "Copy constructor" << endl;
		init(l_input.n_data);
		for (unsigned i = 0; i < n_data; ++i)
			ptr[i] = l_input.ptr[i];
	}
	CustomVector(CustomVector&& r_input)
	{
		cout << "Move constructor" << endl;
		n_data = r_input.n_data;
		ptr = r_input.ptr;

		r_input.n_data = 0;
		r_input.ptr = nullptr;
	}
	~CustomVector()
	{
		delete[] ptr;
	}
	void init(const unsigned& _n_data, const int& _init = 0)
	{
		n_data = _n_data;
		ptr = new int[n_data];
		for (unsigned i = 0; i < n_data; ++i)
			ptr[i] = _init;
	}
};

int main()
{
	CustomVector my_vec(10, 1024);
	CustomVector temp(my_vec);

	cout << my_vec.n_data << endl;
}

 

int main()
{
	CustomVector my_vec(10, 1024);
	CustomVector temp(std::move(my_vec));

	cout << my_vec.n_data << endl;
}

#include <iostream>
#include <vector>
#include <cstdio>
#include <utility>

using namespace std;

class CustomVector
{
public:
	unsigned n_data = 0;
	int* ptr = nullptr;

	CustomVector(const unsigned& _n_data, const int& _init = 0)
	{
		cout << "Constructor" << endl;
		init(_n_data, _init);
	}

	CustomVector(CustomVector& l_input)
	{
		cout << "Copy constructor" << endl;
		init(l_input.n_data);
		for (unsigned i = 0; i < n_data; ++i)
			ptr[i] = l_input.ptr[i];
	}
	CustomVector(CustomVector&& r_input)
	{
		cout << "Move constructor" << endl;
		n_data = r_input.n_data;
		ptr = r_input.ptr;

		r_input.n_data = 0;
		r_input.ptr = nullptr;
	}
	~CustomVector()
	{
		delete[] ptr;
	}
	void init(const unsigned& _n_data, const int& _init = 0)
	{
		n_data = _n_data;
		ptr = new int[n_data];
		for (unsigned i = 0; i < n_data; ++i)
			ptr[i] = _init;
	}
};

void doSomething(CustomVector& vec)
{
	cout << "Pass by L-reference" << endl;
	CustomVector new_vec(vec);
}

void doSomething(CustomVector&& vec)
{
	cout << "Pass by R-reference" << endl;
	CustomVector new_vec(std::move(vec));

}

int main()
{
	CustomVector my_vec(10, 1024);
	doSomething(my_vec);
	doSomething(CustomVector(10, 8));
}

 

#include <iostream>
#include <vector>
#include <cstdio>
#include <utility>

using namespace std;

class CustomVector
{
public:
	unsigned n_data = 0;
	int* ptr = nullptr;

	CustomVector(const unsigned& _n_data, const int& _init = 0)
	{
		cout << "Constructor" << endl;
		init(_n_data, _init);
	}

	CustomVector(CustomVector& l_input)
	{
		cout << "Copy constructor" << endl;
		init(l_input.n_data);
		for (unsigned i = 0; i < n_data; ++i)
			ptr[i] = l_input.ptr[i];
	}
	CustomVector(CustomVector&& r_input)
	{
		cout << "Move constructor" << endl;
		n_data = r_input.n_data;
		ptr = r_input.ptr;

		r_input.n_data = 0;
		r_input.ptr = nullptr;
	}
	~CustomVector()
	{
		delete[] ptr;
	}
	void init(const unsigned& _n_data, const int& _init = 0)
	{
		n_data = _n_data;
		ptr = new int[n_data];
		for (unsigned i = 0; i < n_data; ++i)
			ptr[i] = _init;
	}
};

void doSomething(CustomVector& vec)
{
	cout << "Pass by L-reference" << endl;
	CustomVector new_vec(vec);
}

void doSomething(CustomVector&& vec)
{
	cout << "Pass by R-reference" << endl;
	CustomVector new_vec(std::move(vec));

}

template<typename T>
void doSomethingTemplate(T&& vec)
{
	doSomething(std::forward<T>(vec));
}
int main()
{
	CustomVector my_vec(10, 1024);
	doSomethingTemplate(my_vec);
	doSomethingTemplate(CustomVector(10, 8));
}

 

19.8 자료형 추론 auto와 decltype

 

#include <iostream>
#include <vector>
#include <cstdio>
#include <algorithm>

using namespace std;

class Examples
{
public:
	void ex1()
	{
		std::vector<int> vect;
		for (std::vector<int>::iterator itr = vect.begin(); itr != vect.end(); ++itr)
			cout << *itr;
		for (auto itr = vect.begin(); itr != vect.end(); itr++)
			cout << *itr;

		for (auto itr : vect) //for (const & itr : vect)
			cout << itr;
	}
	void ex2()
	{
		int x = int();
		auto auto_x = x;
		const int& crx = x;
		auto auto_crx1 = crx; //auto는 형을 받아들일 때 const를 떼 버린다.
		const auto& auto_crx2 = crx;  //auto에 const와 &를 받아줘야 한다.
		volatile int vx = 1024;
		//volatile은 값이 자주 변하기 때문에 최적화할 때 뺴 달라는 말.
		auto avx = vx; //여기도 volatile을 떼고 int만 남긴다.
		volatile auto vavx = vx;
		//auto는 가장 기본적인 것만 변환하고, 자잘한 것은 추가해야 한다.
	}

	template<class T>
	void func_ex3(T arg)
	{}

	/*template <class T>
	void func_ex3(const T& arg)
	{}
	*/  //이렇게 추가해야 한다.

	void ex3()
	{
		const int& crx = 123;
		func_ex3(crx);    //여기서 const 와 &를뗴 버린다.
	}

	void  ex4()
	{
		const int c = 0;
		auto& rc = c;
		//rc = 123; //error //여기서 const가 여전히 붙어 있다.
		//여기서 const int의 reference는 무조건 const가 붙어야 한다(아니면 못 가져옴)
		//그래서 이럴 땐 auto가 const를 붙인다.
	}

	void ex5() //amendment = 개정
	{
		int i = 42;
		auto&& ri_1 = i; //l-value  //왼쪽에 l-value가 들어오면 무조건 l-value.
		auto&& ri_2 = 42; //r-value
	}

	void ex6()
	{
		int x = 42;
		const int* p1 = &x;
		auto p2 = p1;   //const int * 까지 찾아줌.
	}

	template <typename T, typename S>
	void func_ex7(T lhs, S rhs)
	{
		auto prod1 = lhs * rhs;   //곱하기를 한 결과값의 형이 어떻게 되는지 모름!

		//typedef typeof(lhs * rhs) product_type; //pre-c++11 'some' compilers
		//일부 컴파일러에서 제공해줬음. 즉, 데이터 타입을 리턴해주는 함수가 있었음.
		typedef decltype(lhs* rhs) product_type;
		//이것이 정식으로 넘어옴. decltype으로.

		product_type prod2 = lhs * rhs;
		decltype(lhs * rhs) prod3 = lhs * rhs; //자료형처럼 바로 사용할 수 있음.
	}

	template <typename T, typename S>
	auto func_ex8(T lhs, S rhs) -> decltype(lhs* rhs)
	{
		return lhs * rhs;
	}
	//decltype(lhs * rhs) func_ex8(T lhs, S rhs)
	//는 컴파일러가 읽어들이는 순서 상 읽을 수 없다.

	void ex7_8()
	{
		func_ex7(1.0, 345);
		func_ex8(1.2, 345);
	}

	struct S
	{
		int m_x;
		S()
		{
			m_x = 42;
		}
	};

	void ex9()
	{
		int x;
		const int cx = 42;
		const int& crx = x;
		const S* p = new S();
		auto a = x;
		auto b = cx;
		auto c = crx;
		auto d = p;
		auto e = p->m_x;
		//e는 그냥 integer가 된다. p는 const지만 복사해서 상관없어지는 것

		typedef decltype(x) x_type;	//int
		typedef decltype(cx) cx_type; //const int
		typedef decltype(crx) crx_type; //const int &
		typedef decltype(p->m_x) m_x_type; //int
		//멤버는 int로 선언되어 있어서.
		//declared type은 선언된 타입 그대로를 다 가져옴.

		typedef decltype((x)) x_with_parens_type;  //add references to lvalues
		typedef decltype((cx)) cx_with_parens_type;
		typedef decltype((crx)) crx_with_parens_type;
		typedef decltype((p->m_x)) m__with_parens_type;
		//reference라 변화가 되면 안된다는 const도 같이 가져옴
	}

	const S foo()
	{
		return S();
	}

	const int& foobar()
	{
		return 123;
	}

	void ex10()
	{
		std::vector<int> vect = { 42, 43 };
		auto a = foo();   //S
		typedef decltype(foo()) foo_type; //const S
		auto b = foobar();
		typedef decltype(foobar()) foobar_type;

		auto itr = vect.begin();
		typedef decltype(vect.begin()) iterator_type;

		auto first_element = vect[0];
		decltype(vect[1]) second_element = vect[1];
	}

	void ex11()
	{
		int x = 0;
		int y = 0;
		const int cx = 42;
		const int cy = 43;
		double d1 = 3.14;
		double d2 = 2.72;

		typedef decltype(x* y) prod_xy_type; //int
		auto a = x * y; //int

		typedef decltype(cx* cy) prod_cxcy_type; //int
		auto a = cx * cy; //in //result is prvalue, 상수이므로 그냥 int

		typedef decltype(d1 < d2 ? d1 : d2) cond_type; // l-value에는 &가 붙는다.
		auto c = d1 < d2 ? d1 : d2;   //그냥 double

		typedef decltype(x < d2 ? x : d2) cond_type_mixed; //double로 형변환이 잘 된다.
		auto d = x < d2 ? x : d2;  

		//auto d = std::min(x, dbl); //error, min은 데이터 타입이 같아야 비교해줌.
	}

	template<typename T, typename S>
	auto fpmin_wrong(T x, S y) -> decltype(x < y ? x : y)
	{
		return x < y ? x : y;
	}
	//여기서 T와 S의 형이 같으면 reference가 붙는게 단점.

	template<typename T, typename S>
	auto fpmin(T x, S y) ->
		typename std::remove_reference<decltype(x < y ? x : y)>::type
		//reference를 제거한 타입을 사용한다.
	{
		return x < y ? x : y;
	}

	void ex12()
	{
		int i = 42;
		double d = 45.1;
		//auto a = std::min(i, d); //error
		auto a = std::min(static_cast<double>(i), d);
		int& j = i;
		typedef decltype(fpmin_wrong(d, d)) fpmin_return_type1;  //double의 reference
		typedef decltype(fpmin_wrong(d, d)) fpmin_return_type2; //그냥 double
		typedef decltype(fpmin_wrong(d, d)) fpmin_return_type3; //그냥 double
	}

	void ex13()
	{
		std::vector<int> vect; //vect is empty
		typedef decltype(vect[0]) integer;
		//실제 수행은 안해서 문제는 안 생김
	}

	template<typename R>
	class SomeFunctor
	{
	public:
		typedef R result_type;
		SomeFunctor()
		{

		}
		result_type operator() ()
		{
			return R();
		}
	};

	void ex14()
	{
		SomeFunctor<int> func;
		typedef decltype(func)::result_type integer; //nested type;
		//함수 안의 nested type도 간편하게 접근 가능.
	}

	void ex15()
	{
		auto lambda = []() {return 42; };
		decltype(lambda) lambda2(lambda);
		decltype((lambda)) lambda3(lambda);
		cout << "Lambda functions" << endl;
		cout << &lambda << " " << &lambda2 << endl;
		cout << &lambda << " " << &lambda3 << endl;
	}

	void ex16()
	{

		//generic lambda
		auto lambda = [](auto x, auto y)
		{
			return x + y;
		};
		cout << lambda(1.1, 2) << " " << lambda(3, 4) << " " << lambda(4.5, 2.2) << endl;
	}
};
int main()
{
	Examples examples;

	examples.ex1();
	examples.ex2();
	examples.ex3();
	examples.ex10();
	examples.ex12();
	examples.ex14();
	examples.ex15();
	examples.ex16();
}