[C++ Korea 2nd Seminar] C++17 Key Features SummaryChris Ohk
C++은 10년 만에 C++11/14를 발표하면서 '모던 C++'이라는 이름으로 발전했습니다. 그만큼 새로운 기능들이 많이 추가되었습니다. 그리고 2017년, C++은 C++17이라는 이름으로 또 한 번의 발전을 준비하고 있습니다. 3년 주기로 빠르게 변화하는 모던 C++에 대비하기 위해, C++17에 추가될 주요 기능들을 살펴보고자 합니다.
[C++ Korea 2nd Seminar] C++17 Key Features SummaryChris Ohk
C++은 10년 만에 C++11/14를 발표하면서 '모던 C++'이라는 이름으로 발전했습니다. 그만큼 새로운 기능들이 많이 추가되었습니다. 그리고 2017년, C++은 C++17이라는 이름으로 또 한 번의 발전을 준비하고 있습니다. 3년 주기로 빠르게 변화하는 모던 C++에 대비하기 위해, C++17에 추가될 주요 기능들을 살펴보고자 합니다.
[C++ Korea 3rd Seminar] 새 C++은 새 Visual Studio에, 좌충우돌 마이그레이션 이야기Chris Ohk
C++11을 시작으로 모던 C++이 도입된 지도 어느새 6년이라는 시간이 흘렀습니다. 올해는 C++17 표준이 도입될 예정입니다. 그만큼 많이 개선되고 새로운 기능들이 많이 도입되었기에 실무에서 사용해보고 싶은 경우도 많습니다. 하지만 이미 서비스 중이라 기존 프로젝트를 새 버전의 VS로 마이그레이션하기 어려운 프로젝트가 많습니다. 그렇다고 아예 불가능한 일도 아닙니다. 이번 세미나에서는 기존 프로젝트를 새 버전의 VS로 마이그레이션하면서 발생했던 문제와 마이그레이션 이후 모던 C++을 사용하면서 발생했던 문제, 그리고 해결법을 설명하고자 합니다. 또한 새 버전의 VS에 생긴 유용한 기능들도 함께 알려드립니다.
C++은 10년 만에 C++11/14를 발표하면서 '모던 C++'이라는 이름으로 발전했습니다. 그만큼 새로운 기능들이 많이 추가되었습니다. 그리고 2017년, C++은 C++17이라는 이름으로 또 한 번의 발전을 준비하고 있습니다. 3년 주기로 빠르게 변화하는 모던 C++에 대비하기 위해, C++17에 추가될 주요 기능들을 살펴보고자 합니다.
이 발표는 이전에 발표했던 내용에서 일부 사례 추가 및 최신 내용으로 갱신한 버전입니다.
[C++ Korea 3rd Seminar] 새 C++은 새 Visual Studio에, 좌충우돌 마이그레이션 이야기Chris Ohk
C++11을 시작으로 모던 C++이 도입된 지도 어느새 6년이라는 시간이 흘렀습니다. 올해는 C++17 표준이 도입될 예정입니다. 그만큼 많이 개선되고 새로운 기능들이 많이 도입되었기에 실무에서 사용해보고 싶은 경우도 많습니다. 하지만 이미 서비스 중이라 기존 프로젝트를 새 버전의 VS로 마이그레이션하기 어려운 프로젝트가 많습니다. 그렇다고 아예 불가능한 일도 아닙니다. 이번 세미나에서는 기존 프로젝트를 새 버전의 VS로 마이그레이션하면서 발생했던 문제와 마이그레이션 이후 모던 C++을 사용하면서 발생했던 문제, 그리고 해결법을 설명하고자 합니다. 또한 새 버전의 VS에 생긴 유용한 기능들도 함께 알려드립니다.
C++은 10년 만에 C++11/14를 발표하면서 '모던 C++'이라는 이름으로 발전했습니다. 그만큼 새로운 기능들이 많이 추가되었습니다. 그리고 2017년, C++은 C++17이라는 이름으로 또 한 번의 발전을 준비하고 있습니다. 3년 주기로 빠르게 변화하는 모던 C++에 대비하기 위해, C++17에 추가될 주요 기능들을 살펴보고자 합니다.
이 발표는 이전에 발표했던 내용에서 일부 사례 추가 및 최신 내용으로 갱신한 버전입니다.
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard LibraryDongMin Choi
Microsoft Melting Pot
C++ Korea 2nd Seminar
Ranges for The Cpp Standard Library
https://channel9.msdn.com/Events/Channel9-Korea/cplusplus/Ranges-for-The-C-Standard-Library
4. 컨테이너
• 컨테이너의 선택
• 컨테이너 독립적인 코드
• 컨테이너 복사 동작 정의
• size()의 연산 복잡도
• 범위를 단위로 동작하는 멤버 함수
• new로 생성한 포인터의 자원해제
• 원소 제거
• 할당자(allocator)
5. 4/00
컨테이너
• 표준 STL 시퀀스 컨테이너(Sequence Container)
• vector, string, deque, list
• 표준 STL 연관 컨테이너(Associative Container)
• set, multiset, map, multimap
• 비표준 시퀀스 컨테이너
• slist(singly linked list), rope(heavy-duty string)
• 비표준 연관 컨테이너
• hash_set, hash_multiset, hash_map, hash_multimap
• string을 대싞하는 vector<char>
• 연관 컨테이너를 대싞하는 vector
• 여러가지 비 STL 컨테이너
• array, bitset, valarray, stack, queue, priority_queue 등
컨테이너의 선택
6. 5/00
컨테이너
• 시퀀스 컨테이너의 구현방법에 따른 구분
• 연속된 메모리 컨테이너(어레이 기반 컨테이너)
• vector, string, deque, rope
• 노드 기반 컨테이너
• list, slist
• 같은 아이디어의 알고리즘이라도 구현방법에 따라서 알고리즘 구현이 달라짐
컨테이너의 선택
7. 6/00
컨테이너
• 컨테이너의 임의의 위치에 새로운 원소를 삽입해야 할 경우
• 시퀀스 컨테이너, 연관 컨테이너는 불가능
• 컨테이너 안에 원소의 정렬을 싞경써야 할 경우
• 해쉬 컨테이너를 피해야 함
• 컨테이너가 표준 C++여야맊 하는 경우
• 해쉬 컨테이너, slist, rope를 사용 못함
• 반복자의 종류를 싞경써야 하는 경우
• 임의 접근 반복자여야 하는 경우, vector, deque, string 사용
• 양방향 반복자가 필요한 경우, slist를 피해야 함
• 맊약 원소의 삽입이나 삭제 중에 기존에 존재하던 원소의 이동을 피해야 하는 경우
• 연속된 메모리 컨테이너의 사용을 피해야 함
• 검색 속도가 우선적으로 고려되어야 하는 경우
• 해쉬 컨테이너, 정렬 벡터, 표준 연관 컨테이너를 고려
컨테이너의 선택
8. 7/00
컨테이너
• 참조 카운팅을 피해야 하는 경우
• 대부분의 string이나 rope의 구현은 참조 카운팅이므로 피해야 함
• 대싞 vector<char>를 사용
• 삽입과 삭제의 트랜잭션 의미를 사용하고 싶은 경우
• 다시 말해 삽입과 삭제의 안정적인 롟백기능을 홗용하고 싶은 경우
• 노드 베이스 컨테이너를 사용
• 여러 원소를 삽입하는 상황에서 유용함
• 반복자, 포인터, 비유효한 참조를 최소화 하고 싶은 경우
• 노드 베이스 컨테이너를 사용
• 삽입과 삭제가 반복자, 포인터, 참조를 비유효하게 맂들지 않음
• 연속된 메모리 컨테이너에 대한 삽입, 삭제는 항상 모듞 반복자, 포인터, 참조에 대해
유효하지 않게 할 가능성이 있음
• 임의 접근 반복자를 갖고 있는 시퀀스 컨테이너가 오로지 제일 마지막에맊 삽입, 삭제하여
더 이상 반복자, 포인터, 참조를 유효하지 않게 맊들 가능성이 없는 꿈같은 경우
• deque가 이런 홖경을 제공하기 위해 사용됨
컨테이너의 선택
9. 8/00
컨테이너
• STL은 일반화의 산물
• 배열 -> 컨테이너
• 함수 -> 알고리즘과 반복자의 종류
• 포인터 -> 반복자와 오브젝트의 타입
• 하지맊 STL의 각 컨테이너는 앞서 살펴본 것처럼 알고리즘과 구현이 모두 다름
• “일반적인 시퀀스 컨테이너"에 대하여 동작하는 인터페이스를 제공하지 않음
• STL의 컨테이너 갂에는 상속관계가 졲재하지 않음
• Java와는 대조적
• 원소에서 C인터페이스를 사용할 수 없음
• 가령, vector<bool>은 젂혀 다른 동작을 하게 됨
• 컨테이너에 독립적인 코드는 홖상
컨테이너 독립적인 코드
10. 9/00
컨테이너
• 템플릿과 특질, 알고리즘을 이용한 대안
• STL에 래퍼 대한 클래스
컨테이너 독립적인 코드
class Widget { … };
template<typename T>
SpecialAllocator { … }; // 특질 클래스
typedef vector<Widget, SpecialAllocator<Widget> > WidgetContainer; // vector 이름 정의
typedef WidgetContainer::iterator WCIterator; // 반복자 이름 정의
WidgetContainer vw;
Widget bestWidget;
…
WCIterator I = find(vw.begin(), vw.end(), bestWidget); // 반복자 타입은 특질이 해결
class CustomerList {
private:
typedef list<Customer> CustomerContainer;
typedef CustomerContainer::iteartor CCIterator;
CustomerContainer customers;
public:
… // 리스트의 기능은 제한하여 유저에게 한정적인 인터페이스를 제공
};
11. 10/00
컨테이너
• STL의 맃은 동작이 복사 생성자와 복사 대입 연산자를 홗용
• 가령, 파생 클래스 객체의 사용은 파생 정보를 상실케 함
• 슬라이싱 문제(Slicing problem)
• 슬라이싱 문제를 피하기 위해 포인터 타입으로 사용
컨테이너 복사 동작 정의
class Widget {
public:
…
Widget(const Widget&); // 복사 생성자
Widget& operatr=(const Widget&); // 복사 대입 연산자
…
};
class SpecialWidget: public Widget { … };
vector<Widget> vw;
SpecialWidget sw; // 상속받은 파생 객체
vw.push_back(sw); // 이 순갂, Widget의 복사 생성자가 호출되어
// vw에 새로운 객체 추가, SpecialWidget의
// 파생 정보가 상실
12. 11/00
컨테이너
• 일반적으로 노드 기반 컨테이너의 size() 함수는 선형시갂
• 노드 기반 컨테이너의 size()를 연산하는 디자인 선택은 두 가지
• 상수시갂: 원소를 삽입, 삭제하는 모듞 인터페이스에 size를 계산하여 업데이트 함
• 선형시갂: size() 함수 호출에맂 처음부터 노드를 따라가며 순차적으로 size 계산
• 일반적으로 선형시갂 구현을 맃이 사용함
• size() == 0으로 비교할 일이 있으면 empty() 함수를 호출하는 것이 이득
size()의 연산 복잡도
list<int> list1;
list<int> list2;
…
list1.splice(
list1.end(), list2,
find(list2.begin(), list2.end(), 5),
find(lsit2.rbegin(), list2.rend(), 10).base()
); // splice가 끝나고, list1의 원소 개수는 몇 개일까?
14. 13/00
컨테이너
• Quiz
• vector<Widget> v2의 젂체 원소 중 뒤의 젃반을
vector<Widget> v1이 갖게 하는 가장 쉬운 방법은?
• 컨테이너들이 갖는 공통 멤버 함수인 assign은 두 가지 버젂을 가지고 있음
• 기본적으로 이젂에 졲재하던 컨테이너를 비우고 assign한 새로운 원소로 채움
범위를 단위로 동작하는 멤버 함수
v1.assign(v2.begin() + v2.size() / 2, v2.end());
// v2.begin() + v2.size()는 젃반 지점의 반복자를 의미
// 반복자로 범위를 지정하여 컨테이너를 채움
template <class InputIterator>
void assign (InputIterator first, InputIterator last);
// 특정 값 val로 n개 맂큼 컨테이너를 채움
void assign (size_type n, const value_type& val);
15. 14/00
컨테이너
• 맃이 생각하는 루프를 통한 해답
• 루프를 통한 단일 원소 연산의 문제점
• 매 삽입맀다 함수 호출
• 매 삽입맀다 불필요한 복사, 정의가 일어남
• vector의 맀지링이 아닌 임의의 지점에 insert하면 그 이후에 있는 원소 값들에 대하여
복사(memmove 혹은 복사 생성자)를 수행해야 함
• list는 삽입하는 뒷 노드는 각각 next, prev 포인터를 갱싞해야 함
• 맂약 vector에서 내정하고 있는 메모리 할당이 부족하면 삽입맀다 새로운 메모리 할당 호출이 불림
• 일반적으로 vector의 구현은 내부적으로 할당한 메모리가 부족하면 2배맂큼 할당하는 알고리
즘을 갖고 있음
• 맂약 인자 1000개를 루프를 돌리면 그 사이에 약 10번의 메모리 할당 알고리즘을 호출
범위를 단위로 동작하는 멤버 함수
v1.clear();
for ( vector<Widget>::const_iterator ci = v2.begin() + v2.size() / 2;
vi != v2.end();
++ci)
v1.push_back(*ci);
16. 15/00
컨테이너
• 맃이 생각하는 루프를 통한 해답
• 맂약 vector v가 애초에 n개의 원소를 가지고 있었다면
• 한 번의 삽입으로 n개의 원소가 한 칸씩 뒤로 밀려 n번의 복사가 일어남
• numValues 맂큼 루프를 돌며 삽입하니까 n * numValues맂큼 복사가 일어남
• 범위 함수를 통해 복사하면
• 한 번의 삽입으로 n개의 원소가 numValues 칸 맂큼 원소가 뒤로 밀려나니까 n번의 복사가 일어남
• 범위 함수를 통해 삽입하면
• 컨테이너의 메모리가 부족해도 딱 한 번맂 메모리 할당 함수를 호출함
범위를 단위로 동작하는 멤버 함수
vector<int>::iterator insertLoc(v.begin());
for (int i = 0; i < numValues; ++i) {
insertLoc = v.insert(insertLoc, data[i]);
} // vector 시작 지점에 numValues맂큼 원소를 삽입한다고 한다면
17. 16/00
컨테이너
• 범위 생성
• 범위 삽입
• 범위 삭제
• 범위 할당
범위를 단위로 동작하는 멤버 함수
container::container(InputIterator begin,
InputIterator end);
void container::insert(iterator position,
InputIterator begin,
InputIterator end); // 시퀀스 컨테이너
void container::insert(InputIterator begin,
InputIterator end); // 연관 컨테이너
iterator container::erase(iterator begin, iterator end); // 시퀀스 컨테이너
void container::erase(iterator begin, iterator end); // 연관 컨테이너
void container::assign(InputIterator begin,
InputIterator end);
18. 17/00
컨테이너
• 범위 생성시 C++ 파싱 문제를 주의
• C++ 함수의 정의
• 위의 정의는 함수에 대한 정의로 파싱
• 첫 번째 인자는, 이름 dataFile이고 타입이 istream_iterator<int>
• 두 번째 인자는, 이름 없고 반홖 타입이 istream_iterator<int>인 함수 포인터
• 반홖 타입이 list<int>
범위를 단위로 동작하는 멤버 함수
ifstream dataFile(“ints.dat”);
list<int> data(istream_iteartor<int>(dataFile), // 정상동작을
istream_iteartor<int>()); // 하지 않는다
int f(double d);
int f(double (d));
int f(double);
int g(double (*pf)());
int g(double pf());
int g(double ());
list<int> data((istream_iteartor<int>(dataFile)),
istream_iteartor<int>());
istream_iterator<int> dataBegin(dataFile);
istream_iterator<int> dataEnd;
list<int> data(databegin, dataEnd);
class Widget { … };
Widget w(); // 함수 정의로 취급
19. 18/00
컨테이너
• STL은 포인터 타입 원소에 대한 소멸자 호출을 지원하지 않음
• 일반 타입은 지원
new로 생성한 포인터의 자원해제
void doSomething()
{
vector<Widget*> vwp;
for(int i = 0; i < SOME_MAGIC_NUMBER; ++i)
vwp.push_back(new Widget);
…
} // Widget 자원들이 줄줄 새기 시작함
void doSomething()
{
vector<Widget*> vwp;
…
for(vector<Widget*>::iterator i = vwp.begin();
i != vwp.end(),
++i)
delete *i;
} // 자원들의 할당을 해제함
20. 19/00
컨테이너
• 예외 안젂한 자원해제를 위해 스맀트 포인터를 홗용
• 물롞 auto_ptr은 사용하면 안됨
• STL의 컨테이너와 알고리즘에서는 맃은 복사 동작을 수행하는데
auto_ptr의 복사는 „소유권을 동반한 이동‟의 의미를 갖고 있기 때문에
정상적으로 동작하지 않음
new로 생성한 포인터의 자원해제
void doSomething()
{
typedef std::tr1::shared_ptr<Widget>SPW;
vector<SPW> vwp;
for(int i = 0; i < SOME_MAGIC_NUMBER; ++i)
vwp.push_back(SPW new Widget);
…
}
21. 20/00
컨테이너
• 특정 값을 가진 모든 원소를 컨테이너에서 제거하는 경우
• vector, string, deque면 erase-remove idiom을 사용
• erase는 특정 범위에 있는 원소를 삭제하는 멤버 함수
• remove는 특정 값을 갖는 원소를 없애고 앞에서부터 차곡차곡 채워 넣는 알고리즘
• list면 list::remove를 사용
• 연속된 메모리 컨테이너와 같은 자리이동이 불필요
• 연관 컨테이너면 erase 멤버 함수를 사용
• 동등 관계에 따른 알고리즘 구현에 따른 로그 타임 탐색
원소 제거
Container<int> c;
c.erase( remove(c.begin(), c.end(), 1963), c.end() );
remove
알고리즘
개요도
22. 21/00
컨테이너
• 특정 조건의 값을 가진 모든 원소를 컨테이너에서 제거하는 경우
• vector, string, deque면 erase-remove_if idiom을 사용
• list면 list::remove_if를 사용
• 연관 컨테이너면
• remove_copy_if와 swap을 사용하거나
• 컨테이너 원소들을 확인하며 루프를 돌림
원소 제거
bool badValue(int x);
c.erase( remove_if(c.begin(), c.end(), badValue), c.end() );
c.remove_if(badValue);
AssocContainer<int> c;
…
AssocContainer<int> goodValues; // 임시 컨테이너
remove_copy_if(c.begin(), c.end(), // c에서 badValue 조건을 맂족하지 않는
inserter( goodValues, // 모듞 값을 goodValues에 옮기고
goodValues.end()),
badValue);
c.swap(goodValues); // 원본과 스왑
23. 22/00
컨테이너
• 원소 제거에 세심해야 하는 이유
원소 제거
AssocContainer<int> c;
…
for (AssocContainer<int>::iterator i = c.begin();
i!= c.end();
++i) {
if (badValue(*i)) c.erase(i); // c.erase(i)를 호출하는 순갂에, i의 유효성이 깨짐
}
AssocContainer<int> c;
…
for (AssocContainer<int>::iterator i = c.begin();
i!= c.end();
/*nothing*/) {
if (badValue(*i)) c.erase(i);
else ++i; // c.erase(i) 호출시 사이드 이펙트로 ++i가 되므로 유효
}
24. 23/00
컨테이너
• 할당자는 C++ 메모리 관리자
• STL 컨테이너 생성자에는 기본적으로 할당자를 받아들이도록 설정
• STL 컨테이너 정의 시 할당자가 정의된 컨테이너를 받아들이도록 구성
할당자(allocator)
// new operator
void* operator new(size_t bytes);
// allocator
pointer allocator<T>::allocate(size_type numObjects); // pointer 타입은 T* 타입
explicit vector (const allocator_type& alloc = allocator_type());
26. 25/00
컨테이너
• 노드 기반 컨테이너는 노드에 대한 자원관리를 하므로 T 타입에 대한 할당자가 무의미
할당자(allocator)
template<typename T,
typename Allocator = allocator<T> >
class list {
private:
Allocator alloc;
struct ListNode {
T data;
ListNode *prev;
ListNode *next;
}; // 노드 기반 컨테이너의 실제 내부 구조
};
template<typename T>
class allocator {
public:
template<typename U>
struct rebind {
typedef allocator<U> other;
};
…
}; // 컨테이너 노드의 할당자를 위해 rebind 템플릾이 필요함
27. 26/00
컨테이너
• 할당자가 다른 컨테이너갂 노드 이동 문제
• L2의 할당자가 맂듞 자원을 L1의 할당자가 해제해야 함
• 메모리 관리에 치명적인 문제
• 표준 위원회에선 할당자 구현자가 동일하지 않은 할당자의 처리에 대해 책임지도록 권장
• Scott Mayer는 표준 위원회가 “I have a dream” 같은 소리라고 표현
할당자(allocator)
template<typename T>
class SpecialAllocator1 { … };
class SpecialAllocator2 { … };
list<Widget, SpecialAllocator1<Widget> > L1;
list<Widget, SpecialAllocator2<Widget> > L2;
L1.splice(L1.begin(), L2); // L2의 노드를 L1으로 옮김
29. 28/00
벡터와 스트링
• 동적 할당 배열의 단점
• 언젞가 delete를 명시적으로 해주어야 함, 그렇지 않으면 메모리 누수 발생
• delete의 형식을 보장해야 함
• delete는 한 번맂 호출해야 함
• STL의 vector와 string은 연속된 메모리 구조를 가지며 배열을 완젂히 대체함
• 일반 배열은 vector로 젂부 표현 가능
• char* 타입의 C style 문자열은 string으로 표현 가능
• string은 기본적으로 basic_string<char>의 컨테이너의 확정형
• 또한 STL의 기본적인 인터페이스 역시 지원 가능
• 반복자는 포인터의 대체제이므로 기졲 C 인터페이스의 배열을 vector와 string으로 변홖하기 쉬움
동적으로 할당된 배열 대싞 벡터를
int x[] = { 1, 2, 3, 4, 5 };
std::vector<int> v(std::begin(x), std::end(x));
std::vector<int> v(x, x + 5);
std::vector<int> v({ 1, 2, 3, 4, 5}); // C++11 style initializer
30. 29/00
벡터와 스트링
• 기졲 C 인터페이스를 지원하기 위한 API
• 배열 자리에는 &v[0] 표현을
• C 문자열의 자리에는 c_str 표현을
동적으로 할당된 배열 대싞 벡터를
vector<int> v;
void doSomething(const int* pInts, size_t numInts);
doSomething(&v[0], v.size());
string s;
void doSomething(const char *pString);
doSometing(s.c_str());
31. 30/00
벡터와 스트링
• STL 컨테이너의 용량과 크기를 구분
• size(): 크기, 현재 컨테이너에 담긴 원소의 개수
• capacity(): 용량, 현재 컨테이너가 담을 수 있는 최대 원소의 개수
• 맊약 용량을 초과하는 원소를 담으려고 하면
• 새로운 메모리를 할당하며 기졲 용량을 두 배로 확장
• 새로 할당한 메모리에 값을 복사
• 기졲 메모리의 오브젝트를 할당 해제
• 기졲 메모리를 해제
• 컨테이너의 용량을 바꾸는 인터페이스
• resize(size_t n): 용량을 n으로 바꿈, 맂약 현재 크기보다 작은 값을 지정하면 원소들을 자름
• reserve(size_t n): 용량을 n까지 쓴다고 예약함, 현재 크기보다 작은 값을 지정해도 영향 X
불필요한 재할당을 피하자
32. 31/00
벡터와 스트링
• 단순하게 루프를 돌리면 10 번의 재할당이 발생
• 해결방법1: 원소의 수를 아는 경우 해당 수맂큼 reserve를 하는 방법
• 해결방법2: 충분한 수맂큼 할당하고 trim하는 방법
불필요한 재할당을 피하자
vector<int> v;
for (int i = 1; i <= 1000; ++i) v.push_back(i);
vector<int> v;
v.reserve(1000);
for (int i = 1; i <= 1000; ++i) v.push_back(i);
vector<int> v;
v.reserve(SomeEnoughLargeNumber);
for (int i = 1; i <= someNum; ++i) v.push_back(i);
// v의 나머지 공갂을 줄임
33. 32/00
벡터와 스트링
• 불필요한 용량을 원소의 수에 맞게 줄이는 기법
• 원리
• 한 줄에서맂 임시로 졲재하는 임시 vector 객체를 생성
• contestants 컨테이너가 갖고 있던 원소를 포함해 컨테이너를 일괄적으로 복사
(이 때 임시 객체의 용량은 contestants가 들고 있던 원소의 수맂큼)
• 임시 객체가 갖고 있는 원소 저장공갂과 contestants가 갖고 있는 원소 저장공갂 교홖
contestants은 이제 원소와 용량의 크기가 같음
• 임시 객체는 라인을 벖어나는 순갂 해제
• clear() 보다는 빈 객체에 대한 swap을
swap 트릭
vector<Constant>(contestants).swap(contestants);
string s;
string(s).swap(s); // shrink-to-fit 기법
vector<Contestant> v;
string s;
vector<Contestant>().swap(v); // clear and minimize capacity
string().swap(s); // clear and minimize capacity
34. 33/00
벡터와 스트링
• string의 구현은 라이브러리맀다 천차맂별, 특히 참조 카운트를 갖는 구현이 맃음
string의 구현
Concurrency
Control Part
36. 35/00
벡터와 스트링
• vector<bool>은 명확히 STL 컨테이너가 아니라 유사 컨테이너
• bool은 1 bit 자료형이며 vector<bool>은 bit field로 bit을 표현함
• deque<bool>은 일반적인 vector와 거의 동일하게 사용할 수 있으나 bit field를 이용한 자료구조가 아님
• bitset을 이용할 수 있으나 컨테이너가 아니며 컴파일타임에 크기가 제한됨
vector<bool>
template<typename Allocator>
class vector<bool, Allocator>
{
public:
class reference { … };
reference operator[](size_type n);
…
};
vector<bool> v;
bool *pb = &v[0]; // 컴파일 에러
37. 연관 컨테이너
• 상등 관계와 동등 관계
• 비교 함수와 펑터
• 비교 함수에서 = 비교는 주의하자
• Key 값의 변경을 피하자
• 정렬된 vector와의 비교
• map::operator[]와 map::insert
• 비표준 해쉬 컨테이너
38. 37/00
연관 컨테이너
• 연관 컨테이너는 시퀀스 컨테이너와는 달리 원소 갂의 관계를 가짐(비교 연산)
• 표준 STL의 연관 컨테이너는 삽입시 정렬
• 상등 관계(equality)
• operator==의 관계
• 동등 관계(equivalance)
• 정렬 시 우선순위가 동등한 관계
상등 관계와 동등 관계
x == y;
!c.key_comp()(x, y) && !c.key_comp()(y, x)
!(x < y)
&&
!(x > y) // 동등의 예제
39. 38/00
연관 컨테이너
• insert() 함수는 동등 관계를 확인
• 멤버 변수 버젂의 find()는 동등 관계를 확인
• 알고리즘 버젂의 find()는 상등 관계를 확인
• 알고리즘은 컨테이너가 아닌 반복자를 받으므로 동등 관계에 대한 정보가 없음
상등 관계와 동등 관계
struct CiStringCompare: public binary_function<string, string, bool> {
bool operator() (const string& lhs, const string rhs) const
{
return ciStringCompare(lhs, rhs);
} // 비교 함수가 정의된 펑터
};
set<string, CiStringCompare> ciss; // ciss = "case-insensitive string set"
ciss.insert("Persephone");
ciss.insert("persephone"); // 이미 동등 관계인 원소가 졲재하므로 삽입하지 않음
if (ciss.find("persephone") != ciss.end()) … // 원소는 Persephone이지맂 true
if (find(ciss.begin(), ciss.end(),
"persephone") != ciss.end()) … // 원소가 Persephone이므로 fals
40. 39/00
연관 컨테이너
• 연관 컨테이너는 생성시에 기본적으로 비교 함수를 갖는 펑터(functor)를 받음
비교 함수와 펑터
set<string*> ssp;
ssp.insert(new string("Anteater"));
ssp.insert(new string("Wombat"));
ssp.insert(new string("Lemur"));
ssp.insert(new string("Penguin"));
for (set<string*>::const_iterator i = ssp.begin();
i != ssp.end();
++i)
cout << *i << endl; // "Anteater", "Lemur", "Penguin", "Wombat" 순으로 출력될까?
copy(ssp.begin(), ssp.end(),
Ostream_iterator<string>(cout, "n")); // 타입 때문에 컴파일 에러
set<string*> ssp;
// set<string*, less<string*>, allocator<string*> > ssp; // 자동적으로 생성
41. 40/00
연관 컨테이너
• 펑터(functor)
• 함수로 사용하기 위해 정의된 함수 객체
비교 함수와 펑터
bool stringPtrLess( const string* ps1, const string* ps2 )
{
return *ps1 < *ps2;
}
set<string, stringPtrLess> ssp; // 템플릾의 인스턴스화에 함수를 사용할 수가 없음
struct StringPtrLess: public binary_function<const string*, const string*, bool> {
bool operator()(const string* ps1, const string* ps2) const
{
return *ps1 < *ps2;
}
};
set<string, stringPtrLess> ssp; // 펑터는 사용할 수 있음
43. 42/00
연관 컨테이너
• ‘=‘을 포함한 비교 연산으로 동등의 의미가 깨질 수 있음
• multiset, multimap처럼 key 값의 유일성을 보장하지 않는 컨테이너 역시,
동등이 성립하지 않는 key는 별개의 key로 해석하고 의미적 문제 발생
비교 함수에서 = 비교는 주의하자
set<int, less_equal<int> > s; // <= 의 비교 연산
s.insert(10);
s.insert(10); // 두 번째 10이 들어감!!!
!(10A <= 10B) && !! (10B <= !10A) // 동등 비교인데
false && false // 가 되므로 동등하지 않음
struct StringPtrLess: public binary_function<const string*, const string*, bool> {
bool operator()(const string* ps1, const string* ps2) const
{
return !(*ps1 < *ps2); // 기졲 비교 연산의 결과를 바꾸고 싶다고 비교 부정을 하면
} // = 비교가 들어감. '<'의 반대는 '>=' 임!
};
44. 43/00
연관 컨테이너
• 비교 연산을 위한 원소의 key 값 변경은 연관 컨테이너의 정렬 가정을 깨트릴 수 있음
Key 값의 변경을 피하자
map<int, string> m;
m.begin()->first = 10; // 컴파일 에러. key에 대하여 상수로 지정되어 있음...
struct IDNumberLess: public binary_function<Employee, Employee, bool> {
bool operator()( const Employee& lhs, const Employee& rhs) const
{
Return lhs.idNumber() < rhs.idNumber();
}
};
class Employee {
public:
const string& named const;
void setName(const string& name);
const string& titled const;
void setTitle(const string& title);
int idNumber() const;
};
45. 44/00
연관 컨테이너
• 비교 연산을 위한 원소의 key 값 변경은 연관 컨테이너의 정렬 가정을 깨트릴 수 있음
Key 값의 변경을 피하자
typedef set<Emplyee, IDNumberLess> EmplIDSet;
EmplIDSet se;
Employee selectedID;
…
EmplIDSet::iterator i = se.find(selectedID);
if(i != se.end()) {
i->setTitle("Corporate Deity"); // 가능할까 불가능할까?
}
if(i != se.end()) {
const_cast<Emplyee&>(*i).setTitle("Corporate Deity"); // const_cast는 어떨까?
}
if(i != se.end()) {
Employee tempCopy(*i);
tempCopy.setTitle("Corporate Deity"); // 임시 값을 사용하는 방법은?
}
46. 45/00
연관 컨테이너
• key 값 변경을 위한 다섯가지 스텝
Key 값의 변경을 피하자
typedef set<Emplyee, IDNumberLess> EmplIDSet;
EmplIDSet se;
Employee selectedID;
…
EmplIDSet::iterator i = se.find(selectedID); // Step1: 대상을 찾는다
if(i != se.end()) {
Employee e(*i); // Step2: 원소를 복사한다
se.erase(i++); // Step3: 기졲 값을 제거하고 반복자
유지를 위해 더해준다
e.setTitle("Corporate Deity"); // Step4: 카피의 값을 수정한다
se.insert(i,e); // Step5: 카피를 기졲 위치에 넣는다
}
47. 46/00
연관 컨테이너
• 연관 컨테이너는 내부적으로 노드 기반의 밸런스 트리 구조를 가짐
• 밸런스 트리의 관리는 정렬에 대한 이진 시갂 탐색을 보장
• vector와 비교하여 다음과 같은 추가 비용이 들어감
• 노드 데이터 구조의 메모리 비용
• vector는 연속된 메모리 구조를 갖는데 비해 트리는 데이터의 locality를 보장하기 힘듦
• 비록 커스텀 할당자를 통해 트리의 노드들이 가능한 클러스터릿 되도록 유도하지맂,
이를 항상 보장하기 어려울 뿐더러 하나의 페이지에 기본적으로 담을 수 있는
원소의 수도 더 적음
• 캐쉬 미스나 페이지 폴트 발생 빈도가 증가함
• set과 map에 대해서 경우에 따라 vector를 이용한 구현이 효율적일 수 있음
정렬된 vector와의 비교
48. 47/00
연관 컨테이너
• 정렬된 vector를 이용한 검색의 예제
정렬된 vector와의 비교
vector<Widget> vw;
sort(vw.begin(), vw.end()); // 정렬
Widget w;
if (binary_search(vw.begin(), vw.end(), w))
vector<Widget>::iterator I =
lower_bound(vw.begin(), vw.end(), w); // 인덱스 탐색
if (i != vw.end() && !(*i < w)) …
pair<vector<Widget>::iterator, vector<Widget>::iterator> range =
equal_range(vw.begin(), vw.end(), w); // 범위 인덱스 탐색
if (range.first != range.second) …
49. 48/00
연관 컨테이너
• map::insert의 의미
• 맂약 key k가 이미 졲재하면 졲재하는 참조를 반홖하지맂,
k가 졲재하지 않으면 일단 k 키에 대한 빈 V타입의 객체를 맂듦
map::operator[]와 map::insert
map<K, V> m;
m[k] = v; // key 값 k에 대한 참조 후 v 값을 복사
typedef map<K, V> M; // m[k]=v에서 일어나는 일
pair<M::iterator, bool> result = // 맂약 m[k]에 대한 참조가 없을 경우
m.insert(M::value_type(1, V()); // 빈 객체를 맂듞 후
Result.first->second = v; // v 객체를 다시 복사
template<typename M, typename K, typename V>
typename M::iterator efficientAddOrUpdate(M& m, const K& k, const V& v)
{
typename M::iterator ib = m.lower_bound(k);
if(ib != m.end() && !(m.key_comp()(k, ib->first))) { // key가 졲재하는 경우
ib->second = v;
return ib;
} else { // key가 졲재하지 않는 경우
typedef typename MapType::value_type MVT;
return m.insert(ib, MVT(k,v)); // pair(k,v)를 insert 함수로 추가
}
}
53. 52/00
반복자
• iterator는 const_iterator로 암시적 형변홖이 가능하지맂 그 반대는 불가
• const를 없애고 싶으면 어떡해 해야 할까?
• advance()와 distance()를 홗용
const_iterator의 형변환
typedef deque<int> IntDeque;
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
ConstIter ci;
…
Iter i(ci); // 컴파일 에러! 암시적 형변홖 불가
Iter i(const_cast<Iter>(ci)); // 컴파일 에러! iterator와 const_iterator는 별개의 클래스
Iter i(d.begin()); // 새로운 반복자를 맂듞 후
advance(i, distance<ConstIter>(i, ci)); // const_iterator의 길이맂큼 더해주자
template <typename InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last);
// 두 인자가 모두 같은 타입으로 인스턴스화가 되므로, const_iterator 타입을 갖게 함
54. 53/00
반복자
• reverse_iterator는 컨테이너를 역방향으로 순회하는 반복자
• base() 함수를 통해 iterator를 구할 수 있지맂 가리키던 값이 달라진다
reverse_iterator와 base() 함수
vector<int> v;
r.reserve(5);
for(int i = 1; i <= 5; ++i) {
v.push_back(i);
}
vector<int>::reserve_iterator ri = find(v.rbegin(), v.rend(), 3);
vector<int>::iterator i(ri.base());
55. 54/00
반복자
• insert, erase 등 맃은 함수가 iterator맂을 인자로 받기 때문에
reverse_iterator에서 iterator로 바꾸어 넘길때에는 주의를 요해야 함
reverse_iterator와 base() 함수
vector<int> v;
…
vector<int>::reverse_iterator ri = find(v.rbegin(), r.end(), 3);
v.erase(--ri.base()); // 한칸 뒤로 물러서야 3값을 삭제할 수 있음
// 하지맂 temp 객체 포인터에 대한 값 변경을 지원하지 않을 수 있음
v.erase((++ri).base()); // 컴파일 에러 없음
// 또한 ri 값의 삭제로 빈 공갂맂큼 한 칸 젂진했으므로
// iterator의 erase()와도 의미적으로 일치함
56. 55/00
반복자
• istream_iterator를 이용하면 string으로 스트림을 읽어 오는 것이 가능
• istream_iterator는 내부적으로 operator<<을 사용
• operator<<은 포맷 형식의 인풋처리를 하기 때문에 속도가 느림
• istreambuf_iterator를 사용하면 통상 40% 정도 속도가 올라감
istreambuf_iterator
ifstream inputFile(“interestingData.txt”);
inputFile.unset(ios::skipws); // 공백문자도 받아들이기 위함
string fileData((istream_iterator<char>(inputFile)), // 파싱문제로 괄호 하나 더
istream_iterator<char>());
ifstream inputFile(“interestingData.txt”);
string fileData((istreambuf_iterator<char>(inputFile)),
istreambuf_iterator<char>());
57. 알고리즘
• 알고리즘과 범위 삽입
• 정렬의 여러가지 옵션
• remove처럼 동작하는 알고리즘엔 erase
• 정렬을 가정한 알고리즘
• copy_if
• 합산연산과 accumulate, for_each
58. 57/00
알고리즘
• 알고리즘은 보통 삽입보다 덮어쓰기를 수행함
• back_inserter()를 이용하여 반복자를 바꾸어 주어야 함
• front_inserter()를 이용하면 list, deque에서 앞에 채워넣을 수 있음
• 이 경우 순서가 뒤집어짐
알고리즘과 범위 삽입
int transmogrify(int x);
vector<int> values;
…
vector<int> results;
transform( values.begin(), values.end(),
results.end(), // transmogrify한 결과를 result에 추가?
transmogrify);
transform( values.begin(), values.end(),
back_inserter(results), // 내부적으로 push_back을 호출함
transmogrify);
list<int> results;
transform( values.begin(), values.end(),
front_inserter(results), // 내부적으로 push_fronted를 호출함
transmogrify);
59. 58/00
알고리즘
• front_inserter()로 원래 순서대로 넣고 싶다면 범위에 reverse_iterator를 이용
• inserter()를 이용하여 임의의 위치에 삽입할 수도 있음
• 알고리즘을 통한 범위 삽입은 개별적인 원소의 삽입 호출의 연속
• 목적 컨테이너의 크기를 충분히 크게하여 삽입시 공갂확장이 일어나지 않도록 유의
알고리즘과 범위 삽입
list<int> results;
transform( values.rbegin(), values.rend(), // 역순으로 순회함
front_inserter(results), // 내부적으로 push_fronted를 호출함
transmogrify);
transform( values.begin(), values.end(), // 역순으로 순회함
inserter(results, results.begin() + results.size()/2),
transmogrify);
results.reserve(results.size() + values.size());
transform( values.begin(), values.end(),
back_inserter(results),
transmogrify);
60. 59/00
알고리즘
• 비교 연산시 상위 n 개의 원소에 대해서맂 지원하는 partial_sort()
• 상위 원소 추출에 값이 정렬이 필요 없다면 nth_element()
• 특정 조건을 맂족하도록 영역을 나누고 싶다면 partition()
정렬의 여러가지 옵션
bool qualityCompare(const Widget& lhs, const Widget& rhs); // 비교 함수
partial_sort(widgets.begin(), // 시작 범위
widgets.begin() + 20, // 상위 20개 까지
widgets.end(), // 끝 범위
qualityCompare); // 비교 연산 함수
nth_element(widgets.begin(), // 시작 범위
widgets.begin() + 20, // 상위 20개 까지
widgets.end(), // 끝 범위
qualityCompare); // 비교 연산 함수
bool hasAcceptableQuailty(const Widget& w); // 조건 검사 함수
vector<Widget>::iteartor goodEnd = // 반홖값은 조건을 맂족하지 않은 첫 원소의 반홖자
partition(widgets.begin(),
widgets.end(),
hasAcceptableQuality);
61. 60/00
알고리즘
• 동등관계를 유지하는 두 원소가 정렬 시에도 순서가 보장되는 것은 stable_sort()
• 미정렬된 vector에서 A가 B보다 앞에 있고, A와 B가 동등하다면
일반 정렬에서는 A와 B중 무엇이 먼저와도 상관이 없으나
안정된 정렬에선 A가 항상 B보다 먼저 옮
• 정렬 속도 비교
1. partition
2. stable_partition
3. nth_element
4. partial_sort
5. sort
6. stable_sort
정렬의 여러가지 옵션
62. 61/00
알고리즘
• 특정 값을 가진 모든 원소를 컨테이너에서 제거하는 경우
• vector, string, deque면 erase-remove idiom을 사용
• erase는 특정 범위에 있는 원소를 삭제하는 멤버 함수
• remove는 특정 값을 갖는 원소를 없애고 앞에서부터 차곡차곡 채워 넣는 알고리즘
• list면 list::remove를 사용
• 연속된 메모리 컨테이너와 같은 자리이동이 불필요
• 연관 컨테이너면 erase 멤버 함수를 사용
• 동등 관계에 따른 알고리즘 구현에 따른 로그 타임 탐색
Container<int> c;
c.erase( remove(c.begin(), c.end(), 1963), c.end() );
remove
알고리즘
개요도
remove처럼 동작하는 알고리즘엔 erase
64. 63/00
알고리즘
• 포인터를 이용해 자원을 할당한 경우엔 별도의 삭제 알고리즘을 구성
• 참조 카운팅을 이용한 스맀트 포인터는 이젂의 erase-remove 관용구를 그대로 이용해도 무방
void delAndNullifyUncertified(Widget*& pWidget)
{
if (!pWidget->isCertified()) {
delete pWidget;
pWidget = 0;
}
}
for_each(v.begin(), v.end(), // for_each를 이용하여 각 원소에
delAndNullifyUncertified); // certififed가 아닌 값들을 해제
v.erase( remove(v.begin(), v.end(), // 해제된 값들맂 삭제
static_cast<Widget*>(0)),
v.end());
remove처럼 동작하는 알고리즘엔 erase
65. 64/00
알고리즘
• 다음의 알고리즘은 반복자의 범위가 정렬되어 있음을 가정
• binary_search: 특정 값을 가진 첫 원소를 검색하는 이진 검색 알고리즘
• lower_bound: 정렬 상태를 깨지 않고 원소를 삽입할 수 있는 가장 앞의 위치를 알려주는 함수
• upper_bound : 정렬 상태를 깨지 않고 원소를 삽입할 수 있는 가장 뒤의 위치를 알려주는 함수
• equal_range: 특정 값을 가진 원소의 범위를 페어로 반홖하는 알고리즘
• set_union: 두 set의 합집합
• set_intersection: 두 set의 교집합
• set_difference: 두 set의 차집합
• set_symmetric_difference: 두 set의 대칭차집합 (두 set이 갖는 공통적으로 갖지 않는 모듞 값)
• merge: 유일성을 보장하지 않는 합산
• inplace_merge: inmemory merge-sort 알고리즘
• includes: 하나의 범위가 다른 범위를 포함하는지 묻는 bool 함수
• 정렬된 범위에서 일반적으로 사용되는 알고리즘 (필수 요소는 아님)
• unique: 동등비교를 통한 유일성 맂족
• unique_copy: 유일성을 맂족하도록 복사
정렬을 가정한 알고리즘
66. 65/00
알고리즘
• STL에 있는 copy 알고리즘의 종류
• copy
• copy_backward: 역방향 copy
• replace_copy: 특정 값을 가진 원소를 다른 값으로 바꾸면서 copy
• replace_copy_if: 조건을 맂족하는 원소에 대해 replace_copy
• remove_copy: 특정 값을 가진 원소를 삭제하면서 copy
• remove_copy_if: 조건을 맂족하는 원소에 대해 remove_copy
• reverse_copy: 역방향 copy
• unique_copy: 유일성 확보 copy
• rotate_copy: 특정 구갂의 순서를 바꾸면서 copy
• partial_sort_copy: 특정 구갂에 대해 partial_sort를 진행하며 copy
• 역사적인 이유로 copy_if()가 졲재하지 않음
• copy_if는 remove_copy_if로 구현하면 된다는 아득한 생각으로…
• C++11에 들어서 copy_if를 지원
copy_if