제 컴퓨터는 코어 4, 논리 프로세서 8개임을 알 수 있습니다. 일반적으로 프로그래밍을 할 때 (하드웨어 스레드 수 x 2) +1이라는 공식으로 나오는 수만큼 멀티 스레드를 만들면 좋다고 합니다. 이 공식으로 계산할 때 std::thread::hardware_concurrency()를 사용하면 좋습니다. 그리고 만약 std::thread::hardware_concurrency()를 사용할 수 없다면 0을 반환하게 됩니다.
안녕하세요. 이번에는 thread(스레드)에 대해서 포스팅하겠습니다. 고성능 프로그램을 만들 때는 멀티 코어를 잘 활용하느냐가 굉장히 중요합니다. 병렬 프로그래밍을 지원하기 위해 C++ 11에서 thread는 프로그래밍을 단순하게 하면서 플랫폼 독립적으로 구현이 가능합니다.
thread 만들기
간단하게 스레드를 만들어 보겠습니다. 코드
#include<iostream>#include<thread>intmain(void){
std::thread Thread1([]()
{
{
for (int i = 0; i < 5; i++)
std::cout << "스레드 1 : " << i << std::endl;
}
});
Thread1.join();
return0;
}
결과
Thread1.join()을 넣는 이유는 스레드가 아직 실ㅇ행 중인데 프로그램이 종료 되기 때문에 join함수를 사용하여 스레드의 실행이 끝날 떄까지 기다리게 한다. 다음은 여러개의 스레드를 돌렸을 때이다. 코드
#include<iostream>#include<thread>intmain(void){
std::thread Thread1([]()
{
{
for (int i = 0; i < 5; i++)
std::cout << "스레드 1 : " << i << std::endl;
}
});
std::thread Thread2([]()
{
{
for (int i = 10; i < 15; i++)
std::cout << "스레드 2 : " << i << std::endl;
}
});
std::thread Thread3([]()
{
{
for (int i = 20; i < 25; i++)
std::cout << "스레드 3 : " << i << std::endl;
}
});
Thread1.join();
Thread2.join();
Thread3.join();
return0;
}
결과
결과를 보니 제가 생각한 결과와 다르게 나왔습니다. 내 예상은 스레드 1이 끝나고 스레드 2가 시작될 줄 알았는데 결과가 뒤죽박죽 섞여 있습니다. 이유는 스레드가 동시에 시작되기 때문입니다. 스레드 1이 끝난 후 스레드 2가 실행되고 싶으면 스레드 1이 끝났을 때 join 함수를 넣어주면 해결이 됩니다.
코드
#include<iostream>#include<thread>intmain(void){
std::thread Thread1([]()
{
{
for (int i = 0; i < 5; i++)
std::cout << "스레드 1 : " << i << std::endl;
}
});
Thread1.join();
std::thread Thread2([]()
{
{
for (int i = 10; i < 15; i++)
std::cout << "스레드 2 : " << i << std::endl;
}
});
Thread2.join();
std::thread Thread3([]()
{
{
for (int i = 20; i < 25; i++)
std::cout << "스레드 3 : " << i << std::endl;
}
});
Thread3.join();
return0;
}
결과
결과를 보면 원하는 대로 스레드 1이 끝난 후 스레드 2가 실행되고 스레드 2가 끝난 후 스레드 3이 실행되었습니다.
마무리
이상으로 포스팅을 마치겠습니다. 다음 시간에는 스레드 식별자와 스레드 교환에 대한 포스팅을 하겠습니다. 감사합니다.
안녕하세요. 이번에는 for문에서 vector.erase()를 사용하는 방법에 대해 포스팅 하겠습니다.
for문에서 Vector erase 사용하기
vector에 1, 3, 2, 1,1, 3, 2, 1,1, 3, 2, 1 를 넣고 값이 1인 값의 인덱스를 erase를 통해 삭제 해보도록 하겠습니다.
#include<iostream>#include<vector>usingnamespacestd;
intmain(){
vector <int> v;
v.push_back(1);
v.push_back(3);
v.push_back(2);
v.push_back(4);
v.push_back(1);
v.push_back(3);
v.push_back(2);
v.push_back(4);
v.push_back(1);
v.push_back(3);
v.push_back(2);
v.push_back(4);
cout << "Vector 값 출력 -----------------------" << endl;
for (int i = 0; i < v.size(); i++)
cout << v[i] << endl;
for (int i = 0; i < v.size(); i++) //vector에서 값이 1인 값을 삭제if (v[i] == 1)
v.erase(v.begin(), v.begin()+i);
cout << "일반 for문 erase vector 출력-----------------------" << endl;
for (int i = 0; i < v.size(); i++)
cout << v[i] << endl;
}
결과
결과를 보고 엇? 뭐지 이러실수 있습니다. 제가 지우고 싶은 값은 vector의 값이 1인 index인데 결과를 보면 값이 1인 index가 전부 지워지지 않았습니다. 왜냐하면 일반적인 증감 for문에 vector.erase를 사용하면 정상적으로 작동하지 않습니다. erase함수는 해당 요소를 지운 후 지워진 요소 뒤의 요소를 가르키기 떄문입니다. 따라서, for문을 돌면서 erase를 통해 index의 vector의 값을 삭제하면서도 계속 for문에서 i값을 증감을 계속 하기 때문입니다. 그렇기 때문에 erase를 하지 않았을 때 i값을 증감시켜야 원하는 결과를 얻을 수 있습니다.
#include<iostream>#include<vector>usingnamespacestd;
intmain(){
vector <int> v;
v.push_back(1);
v.push_back(3);
v.push_back(2);
v.push_back(1);
v.push_back(1);
v.push_back(3);
v.push_back(2);
v.push_back(1);
v.push_back(1);
v.push_back(3);
v.push_back(2);
v.push_back(1);
cout << "Vector 값 출력 -----------------------" << endl;
for (int i = 0; i < v.size(); i++)
cout << v[i] << endl;
for (int i = 0; i < v.size();)
if (v[i] == 1)
v.erase(v.begin() + i);
else
i++;
cout << "erase vector 출력-----------------------" << endl;
for (int i = 0; i < v.size(); i++)
cout << v[i] << endl;
}
for문 안에 i값을 증감하지 않고 erase를 하지 않았을 때 i값을 증감하면 원하는 결과를 얻을 수 있습니다.
마무리
이상으로 포스터를 마치겠습니다. 다음번에는 vector의 sort에 대해 알아보도록 하겠습니다.
안녕하세요. 이번 포스팅에서는 chrono에 대해 알아보도록 하겠습니다. chorono는 C++11에서 추가된 시간에 관련된 라이브러리입니다. 기존의 C 런타임에서 제공하는 time 함수보다 다양한 기능을 제공하며, 사용이 쉽고 정밀도가 훨씬 높습니다. 또한, time함수는 초 단위의 값만 측정할 수 있는데 반해, chrono는 나노 밀리 초 단위까지 측정이 가능합니다. chrono를 사용하면 OS와는 독립적으로 정밀도가 높은 시간 측정이 가능합니다. 또한, 특정 시간 구간에 걸린 시간을 초, 밀리 초, 나노 초 단위로 얻을 수 있으며 시간끼리도 연산이 가능합니다. 예전에 time 함수를 썼다가 시간이 살짝 어긋난 경험이 있어서... 저 같은 경우 시간 관련 코드를 짤 때 chrono를 사용합니다. chrono를 사용하면 OS와는 독립적으로 정밀도가 높은
chrono
#include<iostream>#include<chrono>#include<cmath>intmain(void){
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
int sum = 0;
for (int i = 0;i < 999999999;i++)
sum += i;
std::chrono::duration<double>sec = std::chrono::system_clock::now() - start;
std::cout << "for문을 돌리는데 걸리는 시간(초) : " << sec.count() <<"seconds"<< std::endl;
}
결과
여기서 time_point는 시간상의 한 축을 뜻합니다. for문을 실행하여 다시 현재 시각을 얻은 후에 for문을 시작하기 전 저장한 현재 시각을 빼면 for문을 수행하는 데 걸린 시간을 얻을 수 있습니다.
chrono에서 경과 시 간을 나타낼 때 duration 클래스를 사용하는데, duration은 6가지 시간 단위를 지원합니다. duration 지원 시간 단위
std::chrono::nanoseconds : 나노세컨드. 10억 분의 1초
std::chrono::microseconds : 마이크로 세컨드. 100만 분의 1초
std::chrono::milliseconds : 밀리 세컨드. 1천 분의 1초
std::chrono::seconds : 초
std::chrono::minutes : 분
std::chrono::hours : 시
clock 클래스 chrono에는 system_clock뿐만 아니라 steady_clock와 high_resolution_clock도 있습니다. system_clock이 가장 일반적으로 사용하는 것으로, 시스템 시간을 나타내고 C 런타임의 time_t와 호환이 가능합니다. steady_clock은 물리적 시간처럼 결코 역행하지 않는 시간을 나타냅니다. 즉, steady_clock 이외의 clock 클래스는 time_point를 얻은 후 OS에서 날짜를 과거로 되돌린 후에 time_point를 얻으면 앞에 얻은 시간을 얻지만, steady_clock에서는 그럴 수 없습니다. 따라서 시간의 흐름이 바뀌지 않는 시간을 얻고 싶을 때 steady_clock를 사용하면 됩니다. high_resolution_clock은 Windows 또는 Linux에서 제공하는 정밀도가 가장 높은 시간입니다. 이 클래스는 system_clock이나 steady_clock의 다른 별칭으로 정의되기도 합니다.
chrono를 이용해 timer 만들기
#include<iostream>#include<chrono>#include<cmath>intmain(void){
auto startTime = std::chrono::system_clock::now();
int Sec = 0;
while (1)
{
auto endTime = std::chrono::system_clock::now();
auto sec = std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime);
if (sec.count() > Sec)
{
Sec++;
std::cout << Sec <<" ";
}
}
}
결과
while문을 무한으로 돌려서 콘솔이 시작될 때 무한으로 초를 제는 카운터를 만들어 봤습니다.
마무리
오늘은 시간 관련 라이브러리 중 하나인 chrono에 대해 포스팅해봤습니다. 예전에 개발 중에 기존의 C 런타임 time 함수를 썼는데.. 시간이 미묘하게 맞지 않아서 고생한 적이 있습니다. 그때 chrono의 time함수를 쓰니 정확도가 훨씬 높았습니다. 그래서 C++에서는 웬만하면 시간 관련 함수는 chrono를 사용합니다.
안녕하세요. STL에서 기존에 리스트 자료구조를 사용하는 std::list가 있습니다. std::list가 일반적으로는 사용하기 편하지만, 양방향으로 데이터를 탐색할 필요가 없을 때에는 메모리 사용이나 처리 속도 면에서 아쉬운 점이 있습니다. 실제로 개발을 하다 보면 양방향 리스트가 필요한 경우보다는 단방향 리스트 만으로 충분한 경우가 많습니다. 그래서 이번 포스팅에서는 forward_list의 사용방법에 대해 다뤄보도록 하겠습니다.
forward_list
forward_list의 설계 방침
특별한 이유가 없으면 forward_list는 기존 list 설계에 맞춘다.
설계상 선택지가 여러 개이면 성능을 최우선으로 한다.
std::list의 insert와 erase를 forward_list에서 제공할 수 있지만, 구현이 복잡해지고 성능 면에서 좋지 않으므로 제공하지 않는다.
다른 stl의 컨테이너에 있는 size 함수를 제공하지 않는다. 요소 수를 보존하는 멤버를 가지고 있으면 C 언어에서 구현한 것과 비교해서 불필요한 메모리를 사용하기 때문이다. forward_list와 list의 차이점 forward_list와 list의 차이점
STL의 list 컨테이너와 다른 점
forward_list는 단방향 리스트다. 각 요소는 기 다음 요소를 가리키는 포인터를 하나만 가지고 있다.(list는 양방향 리스트)
list에 비해서 메모리를 작게 사용한다. 각 요소의 메모리만이 아닌 컨테이너 그 자체의 사이즈도 작다. int형에 대해서 list가 12바이트라면 forwar_list는 8바이트이다.(32바이트 기준이고, 64바이트에서 list는 24바이트고 forward_list는 16바이트다.)
list보다 삽입/삭제 속도가 더 빠르지만, 차이는 크지 않다.
한 방향으로만 이동할 수 있다.
삽입과 삭제는 지정한 요소의 다음 요소에 한해서만 가능하다.
forward_list 사용하기forward_list 사용하기
forward_list 사용하기 list컨테이너와 forward_list는 크게 차이가 없습니다. 다만, forward_list는 단방향 리스트라는 것과 다른 컨테이너에서 지원하는 일부 기능이 없습니다. 그 점을 유의하셔야 합니다.
#include<iostream>#include<forward_list>usingnamespacestd;
intmain(void){
forward_list<int> flist;
forward_list<int> flist2;
for (int i = 0;i < 5;i++)//0~4까지 forward_list에 입력
flist.push_front(i);
for (auto value : flist)
cout << value << endl;
flist2.assign(flist.begin(), flist.end());
for (auto value : flist2)
cout << value << endl;
}
결과
데이터 추가하기
insert_after : 저장된 위치 뒤에 새로운 데이터를 추가한다.
emplace_after : 지정된 위치 뒤에 새로운 데이터를 추가한다.
emplace_front : 맨 앞에 새로운 요소를 추가한다.
#include<iostream>#include<forward_list>usingnamespacestd; i
nt main(void){
forward_list <int> flist;
forward_list <int> flist2;
forward_list <int> flist3;
for (int i = 0;i < 5;i++)
{
flist.push_front(i);
flist2.push_front(i);
flist3.push_front(i);
}
cout << "insert_after를 사용하여 추가" << endl;
flist.insert_after(flist.begin(), 99);
for (auto value : flist)
cout << value << " ";
cout << endl;
cout << "emplace_after를 사용하여 추가" << endl;
flist2.emplace_after(flist2.begin(), 99);
for (auto value : flist2)
cout << value << " ";;
cout << endl;
cout << "emplace_front를 사용하여 추가" << endl;
flist3.emplace_front(99);
for (auto value : flist3)
cout << value << " ";
}
결과
데이터 삭제하기
pop_front : 첫 번째 위치의 데이터를 지운다.
rease_after : 지정된 위치 다음이나 지정된 위치 이후의 지정한 범위에 있는 모든 데이터를 지운다.
#include#includeusingnamespacestd;
intmain(void){
forward_list<int> flist;
forward_list<int> flist2;
forward_list<int> flist3;
for (int i = 0;i < 5;i++)
{
flist.push_front(i);
flist2.push_front(i);
flist3.push_front(i);
}
cout << "pop_font로 삭제" << endl;
flist.pop_front();
for (auto value : flist)
cout << value << " ";;
cout << "erase_after로 삭제" << endl;
flist2.erase_after(flist2.begin());
for (auto value : flist2)
cout << value << " ";;
cout << "erase_after로 범위를 지정하여 삭제" << endl;
flist3.erase_after(flist3.begin(), flist3.end());
for (auto value : flist3)
cout << value << " ";;
}
결과
마무리
오늘은 forward_list에대해 포스팅했습니다. 이번 포스팅에서는 sort나 unique를 이용한 중복 제거, merge는 들어가지 않았습니다. 참조 부탁드립니다. 사실 forward_list는 list보다 성능면에서 이점이 있지만, 사용자가 사용하기에는 list가 편하긴 합니다. 그럼 이상으로 포스팅 마무리하겠습니다. 감사합니다.
안녕하세요. 이번 포스팅에서는 C++ STL에서 메모리 관리를 위해 만들어진 shared_ptr과 unique_ptr에 대한 정리를 하도록 하겠습니다. C/C++는 동적으로 할당한 메모리를 사용한 후 해제하지 않으면 메모리 누수가 발생합니다. 보통 개발하다 보면 malloc나 new로 메모리를 할당하고 제대로 해제하지 않아 메모리 누수가 자주 자오죠 ㅎㅎ. 저도 이런 실수를 자주 합니다. 실수 없이 할당된 메모리를 완벽하게 해제해주면 상관없지만 대형 프로젝트에서는 필연적으로 발생합니다.
그래서 Java나 C#을 C++과 비교할 때 메모리 관리에 대한 차이점이 나옵니다. Java나 C#은 메모리를 자동으로 관리합니다. 프로그래머가 메모리를 할당하면 자동으로 해제를 해주기 때문에 프로그래머 입장에서는 편리합니다. 하지만 보통 메모리를 자동으로 관리하는게 어떻게 보면 편하고 좋을 수도 있지만, 꼭 좋은 것만은 아닙니다. Java나 C#은 성능에 문제가 된다면 가차 없이 메모리를 해제하여 가비지 컬렉션이 발생합니다. 가비지 컬렉션이 자주 발생하는 건 리소스를 많이 소비하기 때문에 너무 자주 작동하면 성능상 좋지 않습니다. 하지만 메모리를 자동으로 관리해주는 것은 분명 편리한 기능입니다. 그래서 C++11에서 STL 라이브러리로 메모리를 자동으로 해제시켜 주는 shred_ptr와 unique_ptr이라는 라이브러리가 생겼습니다. 그리고 동적으로 할당한 메모리 해제를 자동으로 관리해주는 것을 스마트 포인터라고 합니다.
shared_ptr
많은 객체나 데이터를 다루다 보면 배열식으로 동적 할당을 해야 할 때가 자주 있습니다. shared_ptr도 배열로 할당이 가능합니다. 간단하게 기존 C++의 new와 shared_ptr을 사용하여 int 배열을 만들어 비교해 보도록 하겠습니다.
#include<iostream>#include<memory>usingnamespacestd;
intmain(void){
shared_ptr<int>shared_ptr_array_test(newint(10));
int* array_test = newint[10];
for (auto i = 0;i < 10;i++)
{
shared_ptr_array_test.get()[i] = i;
array_test[i] = i;
}
cout << "shared_ptr출력" << endl;
for (auto i = 0;i < 10;i++)
cout <<shared_ptr_array_test.get()[i] << endl;
cout << "일반 동적할당 출력" << endl;
for (auto i = 0;i < 10;i++)
cout << array_test[i] << endl;
delete[] array_test;
}
결과
결과를 보면 new와 shared_ptr의 결과 값이 같습니다. 하지만 가장 큰 차이는 new는 필연적으로 delete를 해줘야 하지만 shared_ptr은 자동으로 메모리 해제가 됩니다.
unique_ptr
unique_ptr도 앞에서 설명한 shared_ptr처럼 동적으로 할당된 객체를 관리하는 스마트 포인터 입니다. shared_ptr과 다른 점은 이름에 나타나듯이 객체를 독점적으로 관리한다는 것입니다. 사실 이 말이 처음에는 이해가 가지 않았습니다. 구글에 검색해 봤을 때 unique_ptr은 "unique_ptr를 사용하면 동적으로 힙 영역에 할당된 인스턴스에 대한 소유권을 얻고 unique_ptr 객체가 사라지면 해당 인스턴스를 해제하게 됩니다"라고 설명되어 있습니다. 이 의미를 곰곰이 생각해보니 unique_ptr로 선언된 객체는 누구에게도 공유되지 않고, 스코프에서 벗어날 때 메모리에서 자동으로 삭제가 됩니다. 또한 복사 생성자와 할당 연산자가 구현되어있지 않기 때문에 복사를 할 수 없고, 이동만 가능합니다. 사용하는 방법은 shared_ptr과 비슷하기 때문에 코드는 넘어가도록 하겠습니다.
그렇다면 unique_ptr은 어디에 사용하는게 좋을까요..? 그건 바로 클래스의 멤버 변수로 활용할 때 유용합니다. 소멸자가 호출될 때 알아서 해제되기 때문에 편리합니다.
마무리
이번 포스팅에서는 C++ STL의 스마트 포인터에 대해 다뤄봤습니다. 하지만 포스팅 내용은 많이 허접합니다. 그냥 기본적인 스마트포인터에 대한 개념을 정리한 거라고 보시면 될 것 같습니다. 배열 객체를 다루는 것만 봤는데 실제로 다른 객체를 다루면.. 기본적으로 코드가 길어지고 귀찮아서.. 이 정도만 했습니다. 스마트 포인터에서 객체의 포인터를 얻을 때는 get()을 사용하고, 참조를 얻을 때는 opereator(->)을 사용합니다. 그리고 reset() 함수를 사용하면 기존에 관리하던 객체를 다른 객체로 교체가 가능합니다. 이런 것들을 다뤘어야 했는데... 죄송합니다.
스마트 포인터를 자주 사용하면 분명이 이전보다 메모리 관리가 쉬워집니다. 하지만 생각 없이 사용하다 보면 큰 오류를 범할 수 있습니다. 큰 오류의 원인 중 하나가 순환 참조입니다. 순환 참조는 java나 C#에서도 자주 발생하기 때문에 주의해야 합니다. 순환 참조는 단순하기 때문에 이런 실수는 좀처럼 하지 않겠지만, 대형 프로젝트에서는 주의하지 않으면 충분히 발생할 수 있는 문제이기 때문에 주의가 필요합니다.
이번 포스팅에서는 tuple에 대해 알아보도록 하겠습니다. 튜플은 보통 프로그래밍을 하다 보면 함께 사용되는 변수들을 하나로 묶어서 사용할 때 사용합니다. 특히 함수의 반환 값으로 2개 이상의 값을 반환할 때가 자주 사용됩니다. 보통은 변수들을 묶을 때 구조체(struct)를 사용하지만 구조체를 구현하면 귀찮기 때문에 tuple을 사용하면 편리합니다. 보통 2개의 데이터를 묶을 때는 std::pair을 사용하고 3개 이상이면 std::tuple를 사용합니다.
tuple
간단한 코드를 통해 tuple을 사용해 보겠습니다. tuple을 사용하기 위해선 tuple을 include 해 줘야 합니다. #include
tuple에 int, char, string 세가지 변수를 tuple로 묶었습니다. tuple을 값을 참조할 때 get을 사용합니다. get은 해당 값을 얻을 수 있고, 저장된 값을 변경할 수도 있습니다. 값 얻을 때 : get<0>(User), 값 바꿀 때 : get<0>(User) = 10; 그리고 tuple의 값을 호출할 때 호출하는 자료의 형이 맞지 않으면 인수 목록이 일치하는 생성자의 인스턴트가 없다는 메세지와 오류가 뜹니다.
make_tuple 방금 전 코드는 변수로 tuple을 초기화 했는데 make_tuple를 사용하면 더 편리하게 tuple을 정의할 수 있습니다. 코드를 통해 확인하도록 하겠습니다.
이번에 포스팅에서는 auto와 더불어 유용하고, 사용하기 쉬운 기능 중 하나인 range based for에 대해 포스팅을 시작하겠습니다.
range based for
range based for를 사용하면 반복만을 아주 쉽고 안전하게 사용이 가능합니다. 이 기능은 기존의 visual C++의 특화 기능인 for each문과 비슷합니다. 때문에 기존의 for each를 사용하고 있는 개발자라면 for each를 range based for 문으로 바꾸어 사용하면 됩니다.
간단한 예시코드를 통해 일단 for문과 range based for문의 차이를 알아보겠습니다.
#include<iostream>usingnamespacestd;
intmain(void){
int nNumList[5] = { 5,6,7,8,9 };
cout << "for문" << endl;
for (int i = 0; i < 5; i++)
cout << nNumList[i] << endl;
cout << "ranged based for문" << endl;
for(auto i : nNumList)
cout << i << endl;
}
결과
코드로 보면 별차이 없어 보입니다. 하지만 가장 큰 차이는 for문은 for(int i = 0;i<5;i++) i = 0, i = 5까지 직접 넣어주었고 ranged based for문은 for(auto i : nNumList) nNumList의 처음부터 끝까지 자동으로 범위를 지정해주었습니다. 자동으로 for문의 범위를 지정해준다는 의미는 생각보다 큽니다. 보통 for문을 이용해 배열의 끝까지 자동으로 데이터 값을 얻기 위해서는 개발자가 직접 for문의 범위를 지정해주거나 sizeof를 사용하여 배열의 크기를 얻는 방법이 있는데 range based for문을 사용함으로 반복문의 사용이 편리해졌을 뿐만 아니라 for문을 사용할 때 종료 조건이 잘못되어 메모리를 침범하게 되는 위험도 피할 수 있습니다.
range based for문을 STL 컨테이너도 사용이 가능합니다. 간단한 예시 코드로 알아보겠습니다.
#include<iostream>#include<unordered_map>#include<string>usingnamespacestd;
intmain(void){
cout << "range based for - vector" << endl;
vector<int>NumList;
NumList.push_back(5);
NumList.push_back(6);
NumList.push_back(7);
NumList.push_back(8);
NumList.push_back(9);
for (auto i : NumList)
cout << i << endl;
cout << endl;
cout << "range based for - unoedered_map" << endl;
unordered_map<int, string> NumString;
NumString.insert(make_pair<int, string>(1, "abc"));
NumString.insert(make_pair<int, string>(2, "def"));
NumString.insert(make_pair<int, string>(3, "ghi"));
for (auto i : NumString)
cout << "Num : " << i.first << ", string : " << i.second << endl;
}
결과
기본적으로 STL의 iterator를 지원하는 컨테이너라면 range based for문을 문제없이 사용할 수 있습니다. 그러므로 프로그래머가 자신만의 컨테이너를 만들 때 STL에서 정의한 iterator의 기능을 구현하면 range based for문을 사용할 수 있습니다.
코드를 구현하다보면 for문을 이용해 데이터 요소 값을 바꾸고 싶을 때가 있습니다. 그럴 때는 참조를 사용하면 됩니다. for (auto& i : nNumList)
예시코드
#include<iostream>usingnamespacestd;
intmain(void){
int nNumList[5] = { 5,6,7,8,9 };
cout << "for문" << endl;
for (int i = 0; i < 5; i++)
cout << nNumList[i] << endl;
cout << "ranged based for문" << endl;
for (auto& i : nNumList)
i = 10;
for (int i = 0; i < 5; i++)
cout << nNumList[i] << endl;
}
결과
for문에서 요소 값을 변경하지 못하도록 하려면 const를 사용하면 됩니다. for(auto const i : nNumList)
마무리
저 같은 경우 영상처리 알고리즘을 돌릴 때 보통 for문의 범위를 수동으로 지정하거나 자료형의 sizeof를 통해 크기를 얻어 범위를 지정해서 돌리기 때문에 가끔 메모리 침범해서 벅나는 경우가 많았습니다. 하지만 range based for을 사용하면 개발자의 실수에 의한 메모리 침범을 피할 수 있는 게 가장 큰 장점인 것 같습니다.
#include<iostream>intmain(void){
auto func = [](int Num) {std::cout << Num << std::endl;};
func(99);
func(999);
func(9999);
return0;
}
결과
람다는 변환값(return)을 넘길 수도 있는데, 변환 값(return)의 형은 명시적으로 지정할 수도 있고 암묵적으로 추론이 가능합니다. 그냥 함수처럼 반환 값을 넘기는데 형을 이건 float로 반환해라, 이건 int로 반환해라 할 수도 있고 값을 넘기면 알아서 추론이 가능하게 암묵적으로 추론도 가능하다는 뜻입니다. Example Code
#include<iostream>intmain(void){
auto func1 = []() {return3.14;};
auto func2 = [](float f) {return f;};
auto func3 = []()->float {return3.14;};
float f1 = func1();
float f2 = func2(3.14f);
float f3 = func3();
return0;
}
람다 캡처 람다를 사용할 때 외부에 정의되어 있는 변수를 내부로 사용하고 싶을 면 그 변수를 캡처하면 됩니다. 캡처는 참조나 복사로 전달이 가능하고, 참조할 때는 &를, 복사로 전달할 때는 그냥 변수 이름을 대입하면 됩니다.
보통 C++에서 사용할 때 Call by Value & Call by Reference를 생각하시면 될 것 같습니다. 흠.. STL을 공부하시면 개념은 알 거라고 생각하고.. 넘어가도록 하겠습니다.
vector Num에 1~10까지의 숫자를 넣고 for_each함수에 람다 함수를 넣고 캡처 기능을 써서 nSum에 vector값을 더해 줬습니다. 만약 &nSum을 그냥 nSum으로 복사로 캡쳐를 하게 되면 컴파일 에러가 납니다. 이 때 std::for_each(Num.begin(), Num.end(), [=](int sum)mutable {nSum += sum;}); mutable를 추가 하게 된다면 컴파일 에러 없이 사용이 가능합니다. 하지만 복사로 캡쳐를 했기 때문에 람다 내부에서 변경한 외부 변수의 값은 람다를 벗어나면 원래 값인 0으로 출력이 됩니다.
std::for_each(Iterator first, IIterator last, Function fn); first부터 last전까지 원소들의 각각에 대해 fn을 실행합니다.
Default 캡쳐 람다는 외부의 모든 변수를 참조로 캡처하고 일부는 복사 또는 참조로 캡처가 가능합니다.
int n1, n2, n3, n4, n5;
[&, n1, n2] {};//n3,n4,n5는 참조, n1,n2는 복사
[=, &n1, &n2] {};//n3,n4,n5는 복사, n1,n2는 참조
예를 들어 람다를 [&, n1, n2] {};으로 선언을 했을 때 n1와 n2는 복사로 캡처를 하고, 맨 앞에 &을 선언함으로 나머지 n3,n4,n5는 참조로 캡쳐를 한다는 의미입니다. 반대로 [=, &n1, &n2] {};했을 때 n1과 n2는 참조로 캡쳐를 하고, =을 선언함으로 나머지 n3,n4,n5는 복사로 캡쳐를 한다는 의미입니다.
마무리
람다는 공부하며 느낀 람다의 가장 큰 장점은 STL 중에 find_if나 sort, 그리고 아까 사용했던 for_each 등 알고리즘을 사용할 때 특정 조건자를 사용하려면 함수 객체를 따로 정의해야 했는데, 람다를 사용하니 그 부분이 줄어들었습니다. 확실히 코드의 길이는 줄어듭니다. 하지만 아직 숙련이 부족해서 그런지 익숙하지 않네요.... 분명 잘 활용하면... 좋을 것 같긴한데...