Chapter 16. STL + string Class + etc...

지환·2022년 8월 16일
0

여기 뒷부분으로는 지엽적인 내용도 좀 많고, 하나하나 정리할 시간에 여러번 보면서 익히는게 나을 것 같아서 기억할만한 것들 말로만 슬슬 정리하고, 자세하게는 정리 안해야지.

링크: 여기 보니까 간단하게 잘 정리돼있으니 까먹으면 저기보고 맥락 다시 짚고 하면 될듯


String Class

IO 라이브러리에서 string과 직접적인 연관은 없다.(cin 오버로딩 지원한다던가..)
그래서 사실 string object들은 ostream 함수를 쓰는게 아니라 일반 함수를 쓴다.
string str; cin >> str; getline(cin, str);
operator>>도 사실은 일반함수이다. getline도 마찬가지고..
>>는 티가 안날 뿐임.

string은 class template basic_string을 통해 typedef로 정의된 것이다.

template<class charT, class traits = char _traits<charT>, class Allocator = allocator<charT> >
basic_string {...};

typedef basic_string<char> string;
말고도 typedef basic_string<wchar_t> wstring; 등등 더 있음.

string class는 메모리 할당이 동적으로 알아서 발생하는데, 사실 하나씩 필요한 공간만큼 딱맞게 늘리고 줄이진 않는다.
특정 크기만큼 넉넉하게 할당받는다. 2의 지수승으로 할당받기도하고..
왜냐하면 하나씩 늘리면 뒤에 다른 놈이 공간을 차지하고있으면 옮겨야되는데, 그럼 느려지니까 차라리 넉넉하게 받아두고 그걸 넘어서면 그때 늘리던가 하는 방식임.(안쓰는 메모리도 차지하는게 단점이긴함)
capacity() 이용하면 현재 블록 크기 리턴,
reserve() 이용하면 특정 공간에 대한 최소 할당 크기만큼 할당받음.
책에선 str.capacity(50); 한 뒤에 str.capacity();로 확인해보니 63만큼의 공간이 할당됨.

string class엔 length와 size 멤버함수가 둘 다 있다.
같은 역할을 하는데, size함수는 내부적으로 쓰이기 위함이고,
length는 사용자가 쓰기 쉬운 이름으로 하나 더 만든 것이다.
대부분 사람들이 글의 길이라고하지 크기라고는 안하기때문, 뭘쓰든 상관은 없긴함.

Smart Pointer Template

memory leak의 문제는 우리가 delete 하는걸 까먹거나, 아예 할 수 없는 상황이 생긴다는 것이다.
그래서 앞서 destructor로 해제시키거나, try-catch문 한번 더 사용하는 등 어떻게든 delete가 되도록 했다.
smart pointer template을 이용하면 pointer처럼 특정 메모리를 가리키는 object를 만들 수 있는데(내부 멤버엔 당연히 포인터도 있겠지),
이 탬플릿으로 만든 object가 해제될때 자동으로 본인이 가리키는 공간도 해제되도록 한다.
(object를 heap에 만들지 않는 이상, static이든 automatic이든 알아서 해제가 될 것이고, 그게 해제될때 가리키는 놈도 같이 해제되도록 해주는 것)

그런데 문제가 있는데, 같은 곳을 가리키게 해버리면 해제가 두번 일어나서 오류임.
이를 해결하기위해
1. pointer assign 시에 복사가 수행되도록한다. 즉, 같은 곳을 가리키지 않도록 하는 것. 근데 이러면 포인터를 쓰는 의미가 없지
2. 하나의 smart pointer만이 가리킬 수 있도록 한다.(ownership)
즉, ptr1 = ptr2; 라고 했을때, ownership이 ptr1로 넘어가고, ptr2는 더이상 해당 메모리를 참조하지 못하게된다.
그리고 당연히 메모리해제는 ptr1이 해제될때 일어나도록 한다.
3. reference counting을 이용한다. 특정 memory를 몇개의 pointer가 가리키는지를 체크하는건데, assign이 발생할때마다 카운팅을 1씩 올리면 된다.
그리고 각 포인터 변수가 해제될때 카운팅을 1씩 줄이고, 마지막 남은 포인터 변수가 해제될때 가리키는 memory도 함께 해제되도록 한다.

auto_ptr(검색해보니 C++17이후론 아예 deprecate네)
: 2번 방식 사용. 메모리 해제 두번하는 문제는 발생하지 않지만, 실수로 ownership이 없는 포인터를 사용할 확률이 높다.(안그러기도 어렵고)

unique_ptr
: 마찬가지로 2번 방식을 쓰지만, assign시에 소유권을 넘기지 않고 아예 막아버린다.(auto_ptr보다 안전)
그렇다고 완전 막는건 아니고, assign되는 놈이 임시 value라면 허용한다.
예를들어 함수의 return 값으로 unique_ptr이 넘어왔다면 그 return 값을 assign하는 것은 괜찮다.(어차피 없어지는 값임)
아니면 move semantic을 사용해서 unique_ptr의 ownership을 옮길 수 있다.
std::move() 이용해서 ptr1 = std::move(ptr2); 이런 assign은 괜춘

특정 경우에선 또 허용해주는게 줏대없어 보일수도 있는데,
저렇게 명시해서 assign하는건 내가 목적을 잘 알고 하는 것이니
확실히 auto_ptr처럼 뇌빼고 했을때 문제 발생할 수 있는거랑은 다르지.

(p.977아래부터)
unique_ptr은 parameter로 넘겨줄때 조심해야됨.. reference로 받자.

shared_ptr
: 3번 방식 사용

auto_ptr과 shared_ptr는 new[] 를 통해 할당받은 메모리를 가리킬 수 없다.
new로 할당받은 메모리만 가능.
unique_ptr은 new든 new[]든 상관없이 다 가능.

STL

container들이 공통으로 지원하는 함수가 있다.
begin(), end() 등등

특정 작업들은 containter class의 멤버함수가 아니라 일반함수로 지원하기도한다.
예를들어 find()는 하나만 정의해서 모든 컨테이너가 사용하도록 한다.(이렇게 하는게 공간 측면에선 효율적)
저런 일반함수가 밖에 있는데도 class내에 같은 기능을 하는 멤버함수를 따로 구현하기도하는데, 이는 해당 class에 더 특화된 알고리즘을 사용하기 때문이다.(속도 측면에서 효율적)

find외에 for_each, random_shuffle, sort도 마찬가지로 일반 함수로 지원한다.
sort함수는 두가지 버전이 있는데,
하나는 container가 담는 class에 멤버함수 operator< 를 사용하는 방법(이 버전을 쓰려면 멤버함수로 하든 아니든 해당 object가 operator< 함수를 쓸 수 있도록 해야됨),
다른 하나는 우리가 대소비교하는 함수를 세번째인자로 넘겨주는 방법이 있다.
즉 첫번째 버전은 인자 두개(범위지정하는 iterator)만 받고,
두번째버전은 인자 세개(범위지정하는 iterator + 함수)를 받는다.


Generic Progarmming

STL은 Generic programming 패러다임을 구현한 한 예시이다.
gp도 데이터 추상화와 코드 재활용성을 구현하기때문에 oop와 비슷한게 아닌가 생각할 수 있는데, 둘의 철학은 전혀 다르다.
oop는 데이터를 표현하는데 집중하지만, gp는 알고리즘에 집중한다, 데이터형과 무관한 코드(알고리즘)을 짜는 것이 generic progarmming의 목표이다.
template이 그 목표를 도와주기도하지만, 훨씬 더 세심하게 설계한 구성요소를 추가한다.
(그 구성요소가 이터레이터)

Iterator

generic programming을 위한 요소이다.
template이 알고리즘을 데이터 type에 무관하게 만들어준다면,
iterator는 알고리즘을 container에 무관하게 만들어준다.
즉, generic programming에 있어서 필수요소이다.

find 함수가 있다고 해보자. iterator가 없다면, 배열은 배열대로 linked list는 linked list대로 각자의 find 함수를 가져야한다.
두 container 내부 elements를 진행하는 방식이 다르기 때문이다.(배열은 index, linked list는 link타고 가야됨)
이때 각 container가 내부에 iterator를 가진다면, find는 하나만 작성해도된다. 실제로 위 두 find는 같은 알고리즘이므로 두개를 적는건 비효율적이다.

물론 같은 역할을 하더라도 특정 class에 종속될 수 밖에 없는
begin(), end() 등은 각 container에서 따로 가질 수 밖에 없다.

어떤 알고리즘에서든 해당 알고리즘을 적용할 container 내부를 "어떻게" 순회할지 방법은 중요하지않다.
알고리즘이 container 마다 다르게 내부를 순회하는 대신,
각 container마다 통일된 규격의 iterator를 제공함으로써 algorithm을 하나만 작성할 수 있게 하는 것이다.

그래서 모든 container는 내부에 각 container에 특화된 iterator type을 갖는다.
iterator를 그냥 포인터로 만들든, 아니면 좀 구현이 복잡해서 class로 정의하든, 사용하는 입장에선 상관없다.
약속된 interface만 제공해주면 된다.
class로 정의되더라도 *, =, ==, !=, ++ 등의 기본 포인터가 했던 연산들은 모두 똑같이 지원하도록 한다.
추가로, 각 containor는 past-to-end element를 지원해서 iterator가 제대로 통일되게 작동하도록 한다.

past-to-end 원소가 없으면 마지막 원소인지 판별할때,
linked list는 다음 link가 널인지 봐야되고, 배열은 index를 확인해야되고,,
즉 둘의 규격이 달라진다. past-to-end로 iterator의 요구사항을 완수하는 것
참고로 iterator 사용될땐 전부 이 개념 적용해야된다.
range로 assign하거나 할때 iterator 넘겨줄때 두번째는 past-to-end 위치 넘겨야됨.
end()도 애초에 past-to-end iterator 반환

iterator 종류
알고리즘마다 원하는 iterator연산이 다르다. sort면 random access 지원해야될거고, find는 굳이 그럴 필요 없고.
그래서 용도별로 종류가 나뉜다.
1. input iterator
2. output iterator
3. forward iterator
4. bidirectional iterator
5. random access iterator

cplusplus
각 iterator마다 지원하는 연산이 다르다.
각 container class에선 위 5가지 iterators 중 해당 container의 특성에 맞는 iterator를 정의한다.
종류는 다 달라도 class 내부에선 typedef로 모두 iterator라는 이름을 가진다.
(class 내부에서 typedef로 모두 iterator라는 이름을 가지긴 하지만, cplusplus 같은 문서 보면 어떤 종류인지 알 수 있음)

위에서 말했지만, 알고리즘마다 원하는 iterator 연산이 다르다.
즉, 원하는 iterator 종류가 다르다.
위 표를 보면 알겠지만 각 iterator는 계층이 있으므로, 특정 conatainer가 구현하는 iterator가 algorithm이 원하는 iterator의 연산을 모두 지원한다면 당연히 적용될 수 있다.
예를들어 find 함수는 InputInterator를 필요로 하는데, 이는 어떤 종류의 iterator가 와도 상관없다.
반대로 random access iterator가 필요한 알고리즘에 input iterator가 있는 container는 적용될 수 없다.

template<class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator second, const T & value);
알고리즘 함수들보면 이런식으로 이름만 InputIterator이지 template parameter라서
iterator 종류를 크게 제한하는 것 처럼은 안보인다.
stackoverflow.com/questions/4307271/how-to-check-that-the-passed-iterator-is-a-random-access-iterator
아마 이렇게 std::iterator_traits<Iterator>::iterator_category로 내부적으로 체크하지않을까싶네

책에보니 iterator나 functor 인자는 어떤 concept인지 상기시키기위해
InputIterator, Predeciate 같은 용어로 일부러 받는다고한다.
근데 컴파일러가 그걸 체크할순 없으니 다른 종류를 쓰면 내부적으로 오류가 생긴다네.

concept/refinement/model
: iterator는 강제되는 사항이 아니다. 설계하다보니 나온 것이고, 그것만 사용하도록 제한할 순 없다.
우리가 같은 기능을 구현해도 잘 작동한다. 그저 요구사항일 뿐이다.
이런 요구사항을 "concept"이라고 한다.
또 iterator들 간에 실제 상속은 안되겠지만, 개념적으로 포함된다.
(input은 되게 복잡하게 구현하는데, bidirectional은 포인터만으로 구현해버릴 수도 있음, class간 상속이랑은 다르지)
이렇게 개념적인 상속? 포함? 관계를 "refinement"라고 한다.
특정 concept의 구현을 "model"이라고 한다.
(STL 문건에서 자주 쓰이는 단어라길래 정리해봄)

포인터는 iterators의 요구 사항을 모두 충족.
(애초에 포인터 개념을 일반화한 concept이 iterator, 즉 pointer는 iterator에 포함된다.)
일반 배열의 iterator는 해당 배열 type의 pointer인 셈이다.
따라서 기본 배열은 STL algorithm을 사용할 수 있다.

iterator header file엔 몇몇 predefined iterator가 있다.(전부 class template)
1.ostream_iterator
2.istream_iterator
3.reverse_iterator
4.insert_iterator
5.back_insert_iterator
6.front_insert_iterator

1/2번은 copy() 함수 이용해서 입출력때 사용됨.

3번은 그냥 iterator 방향 뒤집어줌.
얘도 일반 iterator처럼 class마다 고유하게 있긴한데, iterator header에 정의된 reverse_iterator template을 이용해서 만듦.
각 class내에서 본인들 iterator를 template argument로 넘겨줘서 기본 iterator외에 reverse_iterator도 만듦.여기도 보니까 있네

rbegin()==end(), begin()==rend() (값만 같고, type은 다름)
(참고로 rbegin()은 past-to-end 이니 조심)
reverse iterator를 증가시키면 실제론 감소시키기때문에,
copy(dice.rbegin(), dice.rend(), out_iter); 식으로 그냥 쓰일 수 있다.(역순으로 출력되겠지)

4/5/6번은 말그대로 삽입을 도와준다. copy()함수는 주어진 iterator 위치에 그대로 덮어쓴다.
근데 위 iterator들은 자동으로 memory를 대입(자동memory 대입을 이 iterator 내부에서 구현하는게 아니라 간단하게 해당 class의 그런 기능을하는 public function이용함)해서 삽입할 공간을 마련한다.(4번은 지정한위치에, 5번은 뒤에, 6번은 앞에)
즉, 위 iterator를 이용하면 데이터 복사 알고리즘을 삽입 알고리즘으로 변경할 수 있다. 예를들어 copy같은 함수를 이용해 삽입을 할 수 있음.
(4/5번은 고정시간 알고리즘 허용하는 container에만 쓰인다고 하네, 6번은 상관없고. 그래서 4/5번은 잘 찾아서 쓰면 더 빠름)
container의 insert 함수 있는데 왜 insert iterator도 있지?

insert관련 iterator들..
: 보통 iterator라고 하면 class 내부에서 세밀하게 조작해서 통일된 interface를 제공해주기마련인데,
insert관련 iterator들은 iterator header에 template으로 정의돼있다.
back_insert_iterator<vector<int> > back_iter(dice); 식으로
이 insert iterator를 사용하려는 class를 이용해 정의해준다.
그 말인즉, 해당 class의 interface만을 사용해 구현된다는 소리다.
책에도 보면 back_insert_iterator 같은 경우 push_back() 메서드가 있는 class에 대해서 가능하다고 한다.
cplusplus에 보니까 front_insert_iterator는 해당 class의 push_front() 함수 쓰고,
그냥 insert_iterator는 해당 class의 insert()함수 사용한다.
그러므로 해당 함수들 지원하는 class(container)인지 확인해서 사용하면 된다.
(물론 딱 저 함수라고 명시돼있진않고, 원래는 "이런 container에 대해 지원해라" 이정도로만 얘기하겠지만..)
즉 다시 크게 보자면, 이렇게 밖에 따로 빼둔 이유는 일반화가 가능하기 때문일 것이다.
특정 container들의 interface를 활용해서 포인터나 기존 iterator처럼 따라가기만 하는게아닌,
삽입 기능을 할 수 있는 iterator를 만들 수 있도록 한 것이다.

기존 iterator들도 그럼 밖에서 일반화 안되나?
ㅇㅇ 안되지. 걔네는 각 class마다 구현 구체사항 보면서 순회해야되는 애들이고,
insert iterators는 그런 기본 iterator도 활용해서 그 상위에 구현된 셈임.

Container

> concept (개념적으로 container가 충족해야하는 요구사항들을 설명)
container는 단일 object들을 저장.(여기서 object는 built-in type이어도 되고, class object여도 되고)
추가로 저장되는 object는 (1)copy construction과 (2)assignment 가 가능해야한다.
(copy ctor이랑 assignment operator 함수를 public에 정의해두란 소리, built-in type이면 다 지원하니까 상관없고)

각 container 특성/기능 등 정리

+ container의 각 연산에 대한 자세한 구현은 가려질 수 있지만, 시간복잡도는 공개(명시)한다. 여기보면 기본 제공하는 기능과 Time complexity 나옴

각 container들 내부 구현사항(동적할당배열인지 뭔지)은 cplusplus.com가니까 다 적혀있네

> sequence
sequence는 container를 refine하는 concept이다.
말그대로 쭉 길게 연속적으로 존재하는 경우.
forward_list, list, queue, priority_queue, stack, vector
containers 11개 중 6개가 sequnce로 분류된다.

> sequence 요구사항
최소 forward iterator를 내부에 구현해야하고,
지원해야하는 기능은 p.1011 표에 있음.

> Container Adapter
queue/stack container
얘네는는 원소 임의 접근도 막고, iterator도 없다.
따라서 stl algorithm에서 사용 불가
얘네는 우리가 아는 딱 그 연산, push/pop 등만 할 수 있다.

추가로 priority_queue까지 container adpter로 분류한다.
container adpter란?
: 각각 기반 container가 있는데 그것의 interface를 제한하여 만든 container.
stackpriority_queuevector를 기반으로하고,
queuedequeue를 기반으로 한다.

> Associative Containers
값에 key를 결합하여 저장한다. 그리고 그 key를 이용하여 값을 찾는다.
tree 구조를 이용하여 구현되므로 검색/삽입이 sequence에 비해 빠르다.
삽입시 위치를 지정할 순 없다. (각 알고리즘이 알아서 판단해 적당한 위치에 삽입한다. 정렬시켜서 삽입 tree에 정렬해둬야 빨리찾지)

set,multiset,map,multimap
모두 다 key는 고유하다.
set/multiset은 value와 key의 type이 같다.
multiset은 하나의 key에 여러 value가 올 수 있고, set은 1대1.
map/multimap은 value와 key의 type이 달라도 된다.
multimap은 하나의 key에 여러 value가 올 수 있고, map은 1대1.

set은 사실상 key자체를 value로 씀.(vice versa) 둘을 딱히 구분 안하는듯.(말그대로 set,집합이네)
map은 둘을 구분하므로 pair을 인자로 넘겨줘서 insert하기도하고 그럼.

갑자기 생각나서 적는건데,
cplusplus.com에 reference에 member변수는 없길래..
근데 그게 당연한 것 같다. 아마 애초에 표준에 그렇게까지 자세하게 명시하지도 않을거고
data hiding 장점은 이용하는게 맞지. 사용하는 우리 입장에선 interface나 제대로 알면 됨.
대충 어떤 자료구조로 구현되고 어느정도 시간성능은 내라고는 기술돼있으니 그정도로도 충분
애초에 interface만 명시되고, 구현은 하는 사람마다 다를테니 막 보려고할필요도 없을듯
(최적화할거고 내가 딱 쓰려고 마음먹은거 자세히 보려는거면 볼 필요는 있겠다만)

> unorded associative container
associative container처럼 key랑 value 쓰는데,
차이는 hashtable로 구현한다는 것(그래서 unorderd구만)
hashtable이 tree보다 삽입/삭제가 빠르다.

unordered_set, unordered_multiset, unordered_map, unordered_multimap


Functor (함수객체)

iterator가 포인터의 일반화개념이었다면, functor는 함수의 일반화 개념이다.
함수처럼 func1(arg) 식으로 사용할 수 있는 것들을 포함한다.(배운대로임)

  1. 일반 함수 이름
  2. 함수 포인터
  3. () operator overload한 class의 "object"
C에선 함수 argument로 함수 포인터만을 받았다면,
여기선 functor로 범위를 확장.(operator()있는 object까지 확장)

STL algorithm들보면 마찬가지로 type parameter로 generic하게 functor을 받음.
그렇게 object이든 함수포인터이든 받을 수 있게 하는거임.

Functor Concept
generator : argument가 없는 functor
unary function : argument 하나로 호출
binary function : argument 두개로 호출
predicate : bool을 return하는 unary function
binary predicate : bool을 return하는 binary function

STL 함수들은 위 개념으로 Functor을 요구.
예를들어 sort의 한 버전은 binary predicate를 요구(보통은 두 수 크기비교)
(이런 STL함수들은 본인들이 사용하는 인자 수 함수를 넘겨줬다고 생각하고 바로 사용해버리므로 요구조건 당연히 맞춰줘야됨)

※Functor를 굳이 class object로?
사실 C에선 전부 함수로 처리했어서 이런 생각이 들었었다.
왜 Class안에 또 함수를 만드나, 왜 일을 두번하나.
: 함수 말고 class로 만드는 장점은 또 다른 변수를 저장할 수 있단 것이다.
그 장점을 이용해 adaptor functor을 만들거나, adaptable functor을 만들때 사용한다.

※class object인 functor 쓸 때 헷갈리면 안되는게, 그 type은 functor가 아니다.
그걸 이용해 만든 ""object""가 functor의 역할을 하는 것이니 주의
(`TooBig<int>(10)(15)` 식으로 ctor로 바로 object만들고 사용 가능(오 참신해서적어봄)
대부분은 그냥 정상적으로 object 만들어서 쓰거나, 인자 넘겨줄때 바로 만들어서 넘겨줌)

predefined functor
functional header에 미리 정의된 functor가 있다.
정확히 말하자면 functor을 만들 수 있는 class template들이 있다.(목록: classes에 operator classes 부분 보면 됨)
언제쓸까?

1) 기본 type들의 functor을 만들때 쓰면 좋다.
double type은 + 연산이 내부적으로 구현돼있기때문에,
만약 double의 덧셈 연산 functor(좁게말하자면 함수)을 넘겨주려고해도 그런 함수가 없어서 못넘겨준다.
우리가 임의로 double add(double x, duoble y){return x+y;} 를 만들수도 있지만,
predefined를 이용해 plus<double> add;라고 해버리면 double형 덧셈 연산을 하는 add functor가 만들어진다.
(내부 구현은 위 함수와 똑같다.)
참고로 여기서 넘겨줄때 sort(~, plus<double>); 이렇게 하면 안된다.
functor는 object이지 type이 아니다. sort(~, plus<double>()); 로 해야함.

2) class type이더라도 보통은 사용한다.(해야한다.)
잘 생각해보면, STL 함수들이 원하는 functor는 operator() 일텐데, 보통 class 내에서 +, -, < 같은 연산만 구현할 뿐이지 "operator()는 덧셈을한다!" 이렇게 정해놓고 operator()가 덧셈을 하도록 구현하진 않는다.
따라서 마찬가지로 predefined를 통해 functor을 만들어서 넘겨주면 좋다.

말했듯이 STL 함수들이 functor을 요구하기도 하기때문에 그럴때 위 방법으로 functor을 간단하게 만들어서 넘겨준다.
아니면 뭐 손으로 직접 만들든가

> adaptable functor & function adaptor
특정 STL 함수가 functor을 요구하는데, 우리가 원하는 기능을 하는 funcor가 미리 정의돼있다고하자.
근데 그 functor는 argument가 2개필요한데 요구되는 functor는 argument가 1개필요하다면?
이럴때는 adaptation이 필요하다.
(해당 조건을 만족하는 함수를 따로 만들지않고 기존 함수를 조정(code reusing)한다.)

1) class를 새로 정의해, class 내부에서 따로 기존 functor을 호출하며 argument는 하나만 받는 멤버 함수를 정의한다.
class에 정의하는 이유는 data member를 이용해 나머지 한 argument를 저장하기 위함이다.
일반 함수를 그냥 argument 하나짜리 따로 만들어도 되겠지만, code reusing에 초점 맞춘듯

2) acceptable functor라면, binder1st,binder2nd,bind1st,bind2nd를 이용해서 기존 functor을 사용해 argument 1개인 버전을 만든다.(간편)

acceptable functor??
위에서 말했던 predefined template classes로 만든 functors는 "모두" acceptable functor이다.
이게 뭐냐면 그냥 class에 typedef로 return/argument type 정보를 포함하는 것이다.
(그래서 predefined functor가 class였다. 이 정보 포함 안할거면 간단하게 함수로 해도 됐음)
왜냐하면 해당 정보가 있어야 binder 같은데서 argument를 특정 값으로 묶어서 argument 1개짜리 functor를 만들 수 있다.
(직접 binder짜보면 앎. 저 정보 없이 하려면 새로만든 class내의 member 함수 return type 적을때 바로 막힘.)

binder 종류는 변환된 acceptable functor(즉 class로 반환)을 반환하고,
bind 종류는 더 간단하게 함수로 변환해서 반환한다.
(자세한 작동원리 궁금하면 cplusplus보면 금방 느낌옴)

근데 보니까 위에 애들 C++11 이후로 다 deprecated고, bind 라는 놈으로 다 통일된듯.

요약: class object를 함수형태로 사용할 수 있게 되면서 그것도 포함하는 functor개념 등장.
몇몇 STL 함수는 argument로 특정 요구사항(매개변수 등)을 만족하는 functor 요구.
해당 type에 대해 원하는 기능을 하는 functor가 없다면 따로 함수로 정의해도되고
기존에 정의된 template class를 이용해서 functor을 만들 수 있다.

기존에 정의된 functor가 STL 함수의 요구사항 충족 안한다면(ex. 매개변수 개수다름),
재정의하거나, binder/bind 를 사용한다.

Algorithms

1) 알고리즘 일반화를 위해 template을 사용하고
2) 컨테이너 내부 데이터 접근 일반화 표현을 위해 iterator을 사용한다.

iterator가 있다면 container가 아니어도 algorithm을 활용할 수 있다.
string도 그렇고,, 원한다면 직접 만들어줘도 됨.
iterator라는 통일된 툴덕에 다른 종류의 container여도 copy()함수를 사용하거나 할 수 있다.

각 알고리즘별로 그룹화한다.

특정 알고리즘들은 in-place algorithm과 copying algorithm으로 분류된다.
in-place는 알고리즘 종료 후 기존 데이터 자리에 값들이 오고,
copying은 다른 위치에 값이 온다.
두 버전이 다 존재하는 algorithm은 뒤에 함수 이름 뒤에 _copy를 붙여서 구분한다.

뒤에 _if가 붙은 algorithm 함수들은 container의 원소들에 해당 함수 적용 결과에 따라 동작을 수행한다.
ex. replace_if()

member함수 VS 일반함수
일반 알고리즘으로도 함수가 있고, 같은 기능을 하는 멤버함수도 있다면,
보통은 member 함수가 더 좋다.

멤버 함수는 직접 메모리 접근해서 container 크기도 조정할 수 있고, 더 최적화된 알고리즘일 것이다.
실제로 일반 remove() 알고리즘 함수를 list container에 대해 적용하면 길이는 변경이 안되지만, 멤버함수는 삭제한 원소를 빼버리고 길이도 변경해버린다.


vector & valarray & array

vector는 container로 algorithm과 협력할 수 있게 STL에 포함되는 놈이지만,
valarray는 STL에 포함도 안되고 수학계산에 최적화된 type이다.

보면 +, * 등등 다 overloading 해뒀고, 다른 math 함수들이랑 연계도 잘됨.
push_back 같은거 안되는게 단점이긴 하지만, interface가 단순하다.

interface 단순하다고 무조건 빠른건 아니라고하네,
대부분 배열이랑 같게 표현하는데 배열이 그렇게 빠른건 아니라서..
특정 cpu에선 레지스터 뭐뭐 좀 빠르게 해준다는데 그런데선 좀 빠를수도 있다고함.

말고도 indexing할때 slice 같은 놈이랑 연계해서 쓰일 수도 있다고 함.

> initializer_list
initializer_list header의 initializer_list class template를 통해 가변 길이의 데이터들을 다룰 수 있다.

vector 같은 container들의 ctor 버전 중 하나는 initializer_list type을 인자로 받음으로써 {1,2,3} 같은 초기화 형식을 처리할 수 있는 것이다.
물론 배웠듯 언어자체에서 기본 제공하는 {}도 있다.
그래서 만약 특정 class에 initializer_list 이용한 ctor이 있다면 std::vector<double> a{10}; 같은 경우엔 기본 제공{}가 아닌 initializer_list ctor을 사용하도록 한다.
두 경우 다 narrowing은 안된다.


Summary

concept이라는 것은 requirements의 집합이다.
concept의 실제 구현을 model이라고 한다.
다른 concept을 기반으로 하는 것을 refinement라고 한다.
예를들어 bidirectional iterator는 forward iterator concept의 refinement이다.

일반 non member algorithm 함수가 존재하는데, class 내부에 member함수가 따로 존재한다면, 그에 특화된 알고리즘일 수도 있지만 해당 class에서 non member algorithm함수의 iterator 조건을 만족하지 못하는 경우일 수도 있다.
(그런 경우라면 무조건 멤버 함수를 사용해야함)

algorithm함수를 nonmember로 만듦으로써 class마다 모든 함수를 만들 필요도 없어졌고,
iterator 조건 만족하는, STL이 아닌 애들도 사용할 수 있게 한다.

Chapter Review

string이 사용 측면에서 나은 점
1. 자동 메모리 관리
2. assign 가능

smart pointer 주의:
new로 할당받은 memory만 가리킬 수 있다.
애초에 heap 영역 메모리 해제 문제로 생긴 놈임.

STL 기본 algorithms 사용시 iterator 종류 꼭 체크해야한다.
1. 맞지 않다면, 내부 member 함수를 사용해야한다.
2. 아니면, 사용가능한 conatiner에 복사해서 non member algorithm 사용하고, 다시 본래 container로 복사해 올 수 있다.
예를들어 list는 algorithm의 sort를 사용하지 못한다.(iterator때문에)
이럴땐 list container 내부의 sort를 사용하거나, list를 vector에 복사해서 sort하고 다시 list로 복사해올 수 있다.

왜 pointer대신 iterator?
: class 내부를 접근하려면 일반 pointer와 pointer 연산만으론 해결안될 수 있다.

왜 STL 설계자들은 iterator을 상속관계로 구현하지 않았을까?
: generality를 위해서 그런 것이다.
그런 틀로 강제해버리면, iterator 중 하나인 pointer도 accept안되고, 직접 class와 iterator을 구현하고 싶을때도 해당 틀을 가져와서 사용할 수 밖에 없게 된다.
(그래서 보면 InputIterator 라는 parameter 이름으로 표시정도 수준으로만 해줄 뿐인듯)

0개의 댓글