#!if 문서명2 != null
, [[라이브러리]]
#!if 문서명3 != null
, [[]]
#!if 문서명4 != null
, [[]]
#!if 문서명5 != null
, [[]]
#!if 문서명6 != null
, [[]]
1. 개요
||<-12><tablewidth=80%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>
<vector>
||[include(틀:C++ 요소, head_keyword=template, template_p0=T, template_p1=[Allocator])]
| |||||||||||
<bgcolor=#20b580> | |||||||||||
<colcolor=#f0f0f8,#e1e1e1>C++의 초기부터 존재한 모듈 는 클래스 std::vector<T, [Allocator]> 및 관련 유틸리티를 제공한다. |
<vector>
std::vector
는 특정 자료형 T
를 여러개 담을 수 있는 배열을 추상화한 자료구조다. 일련의 메모리에 순차적으로 순회/삽입/삭제/무작위 접근이 가능한 인터페이스를 제공한다. 이 클래스는 내부적으로도 실제 배열처럼 단일한 메모리 공간에 연속적으로 값을 저장한다. std::vector
뿐만 아니라 전역 함수를 비롯한 여러 유틸리티를 포함하고 있다. 연산자 오버로딩 덕분에 C언어의 배열과 거의 비슷하게 사용할 수 있다.std::vector
[1]는 C++에서 가장 빈번하게 사용되는 클래스 템플릿 중의 하나다. 가장 기초적인 기능이지만 벌써 템플릿의 기능을 사용해야 한다는 점을 알아둬야 한다. 다행히도 복잡한 응용이 수반되는 게 아니라, 변수 선언 단계에서 자료형을 명시하는 선에서 그친다. 사용자가 한번 자료형을 명기하고 나면 이후엔 템플릿과는 큰 연관이 없다.2. 포함된 기능
#!syntax cpp
std::vector<int> list;
기본적으로 벡터는 첫번째 템플릿 매개변수에 저장하고 싶은 자료형을 전달하고 선언한다. 마치 int array[N];
처럼 선언한 것이다. 그러나 배열과는 달리 크기를 명시하지 않아도 된다. 포함된 값의 개수가 변할 수 있는데 이를 위하여 동적 할당을 사용하기 때문이다.#!syntax cpp
std::vector<int> list({ 0, 1, 2, 3, 4 });
std::vector<int> list{ 0, 1, 2, 3, 4 };
/* C++17부터 가능한 연역 선언 방식 */
std::vector list({ 0, 1, 2, 3, 4 });
std::vector list{ 0, 1, 2, 3, 4 };
배열의 경우와 같이 initializer_list<T>
를 생성자에 전달해서 처음에 값을 저장할 수 있다.#!syntax cpp
// vector(size_t count, const T& value = T())
std::vector<int> list(5, 0);
또한 size_t 숫자와 반복해 저장할 인자값을 전달하면, 해당 인자는 벡터 안에 지정한 size_t 수만큼 복사되어 저장된다. 중괄호랑 같이 쓰는 경우 위의 initializer_list<T>
를 쓴 생성자와 구별할 수 없기 때문에 주의해야 한다.2.1. 예제 1: 값의 쓰기와 읽기
가장 먼저 알아볼 내용은 벡터의 기본적인 사용법이다.||<-12><tablewidth=80%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>
||
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{constexpr}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{contexpr}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{void}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br]
#!if do_pre_linebreak = (pre_lnb != null)
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{push_back}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{T}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{item}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br]
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}
||
| C++11 | ||||||||||
인자를 하나 받아 벡터의 맨 뒤쪽에 덧붙인다. | |||||||||||
<bgcolor=#20b580> |
#!syntax cpp
std::vector<int> list;
// (1) push_back(T&&)
list.push_back(20);
int e = 22;
// (2) push_back(const T&)
list.push_back(e);
// (3) push_back(T&&)
list.push_back(24);
// `list` == { 20, 22, 24 }
// `num`의 값은 3
size_t num = list.size();
벡터에 원소 추가를 하려면 push_back
멤버 함수를 사용할 수 있다. 이 멤버 함수는 lvalue와 rvalue 둘 다 받도록 오버로딩 돼있다.벡터의 뒤쪽 끝에 삽입하는 이 함수의 성능은 [math(O(1))]로 매우 빠르다. 하지만 내부 메모리가 미리 할당되있어야 한다는 전제가 있다. 이에 대해서는 뒤쪽 문단에서 설명한다.
#!syntax cpp
struct Pair
{
explicit Pair(int = 0, int = 0);
};
std::vector<Pair> list;
// (1) Pair가 Pair(5, int = 0)의 값으로 생성됨.
list.emplace_back(5);
// (2) Pair가 Pair(2, 40)의 값으로 생성됨.
list.emplace_back(2, 40);
// (3) Pair가 `pairvalue`로부터 Pair(8, 3)의 값으로 복사 생성됨.
auto pairvalue = Pair(8, 3);
list.emplace_back(pair);
// (4) Pair가 Pair(10, 5)의 값으로 이동 생성됨.
list.emplace_back(Pair(10, 5));
// (5) Pair가 Pair(int = 0, int = 0)의 값으로 생성됨.
list.emplace_back();
// list = { Pair(5, 0), Pair(2, 40), Pair(8, 3), Pair(10, 5), Pair(0, 0) }
emplace_back
멤버 함수는 특정 인자를 `T`
의 생성자로 전달해서 원소를 할당하고, 추가하는 기능을 한다. 때문에 생성자에 맞게 다수의 인자를 전달할 수 있다. 만약, 원시 자료형 처럼 하나의 값만 가지고 있으면 push_back
과 기능 상의 차이는 없다. 특기할만한 점은 만약 생성자에 매개변수를 하나도 안 넣어도 되면 마찬가지로 emplace_back
에도 아무 인자도 안 넣어도 된다. 알아서 기본 생성자를 호출해서 원소를 추가한다.#!syntax cpp
std::vector<float> list{ 6.0f, 12.0f };
float& reflast = list.emplace_back(18.0f);
// list == { 6.0f, 12.0f, 18.0f }
또한 emplace_back
은 추가한 원소의 좌측값 참조자를 반환한다.#!syntax cpp
// vector(size_t count, const T& value)
// (1) 벡터에 4개의 0이 원소로 생성됨.
std::vector<int> list(4, 0);
// (2) 벡터의 첫번째 원소가 3으로 생성됨.
list[0] = 3;
// (3) 벡터의 두번째 원소가 2로 생성됨.
list[1] = 2;
// (4) `value`의 값은 3
// list == { 3, 2, 0, 0 }
int value = list[0];
벡터는 보통의 배열 처럼 [Index]
(하위 참조 연산자, Subscript Operator)를 써서 값의 읽기와 쓰기를 할 수 있다.#!syntax cpp
// vector(size_t count, const T& value)
// (1) 벡터에 9개의 10이 원소로 생성됨.
std::vector<int> list(9, 10);
// (2) 다섯 번째 원소의 참조자 획득
int& refmiddle = list[4];
// 상동
auto& refmiddle = list[4];
// (3) list == { 10, 10, 10, 10, 100, 10, 10, 10, 10 }
refmiddle = list[2] * list[8];
벡터에서 임의의 번호대로 값을 읽을 수 있다. []
를 사용한 하위 참조(Subscript)는 좌측값 참조자를 반환한다. 원소의 참조자를 쓰면 벡터 인스턴스의 직접적인 참조 없이도 값을 읽고 쓸 수 있다. 성능은 배열과 같이 [math(O(1))]로 빠르다.참고로 동적할당을 사용하는 벡터의 특징 상 내부적으로 사용하던 메모리가 해제될 수 있다. 그 경우 원소의 참조자도 해제될 수 있으므로 주의해야 한다. 이에 대해서는 후술한다.
#!syntax cpp
// (4) clist == { 2, 4, 8, 16 }
const std::vector<int> clist{ 2, 4, 8, 16 };
// (5) 상수 벡터는 상수 참조자를 반환함.
const int& reflast = clist[3];
// 상동
auto& reflast = clist[3];
당연히 불변 벡터는 불변 참조를 반환한다.#!syntax cpp
std::vector<char> list{ 'H', 'e', 'l', 'l', 'o' };
// (1) `ptr`은 `char*`
// 내부 배열의 포인터를 반환한다.
auto ptr = list.data();
// (2) `v0`는 `char&`
// `v1`의 값은 'H'
auto& v0 = list.first();
// (3) `v1`은 `const char&`
// `v1`의 값은 'o'
const auto& v1 = list.last();
// (3) `v2`는 `char&`
// `v2`의 값은 'l'
// 완벽한 전달 사용, [] 연산자와 똑같다.
auto&& v2 = list.at(2);
그외에 값을 읽어들이는 멤버 함수가 더 있다. at(size_t Index)
의 경우 []
연산자와 똑같은 역할과 기능을 한다.data()
멤버 함수를 쓰면 내부의 포인터에 접근할 수 있다. 하지만 이는 성능을 위해 직접 포인터에 접근하도록 해주는 것이며, 포인터가 가리키는 데이터를 수정하는 건 지양해야 한다. const T*
라고 생각하고 사용해야 한다.#!syntax cpp
std::vector<int> list;
// 런타임 오류!
list[4] = 10;
벡터의 하위 참조 연산자는 벡터의 크기를 벗어난 위치를 참조하면 런타임 오류를 발생시킨다. 컴파일 오류가 아니라 런타임 오류이므로 알아두는 게 좋다. 벡터도 C++20
부터 상수 표현식을 지원하나, 동적 할당은 컴파일 시점이 아니라면, 직접 접근하기 전까지는 상태를 알 수 없다.2.2. 예제 2: for 반복문
일반적인 배열처럼 벡터도 원소를 열거할 수 있다. 이때 열거하는 방법은 크게 세가지가 있다. 첫번째는 전통적인 인덱스를 사용한 열거. 두번째는 순회자(Iterator)를 사용한 열거. 세번째는 범위 기반 반복문을 사용한 열거다.#!syntax cpp
std::vector<int> list;
size_t cnt = list.size();
for (size_t i = 0; i < cnt; ++i)
{
auto& element = list[i];
// ...
// push_back, emplace, emplace_back, pop_back 등을 실행하면 안됨.
}
상기 코드는 인덱스 기반의 열거 방식을 보여주고 있다. 보통의 배열을 쓰는 방법과 다르지 않다. 벡터의 크기를 미리 임시 변수로 얻어오는 게 좋다. 왜냐하면 반복문을 평가할 때마다 size()
도 같이 실행하기 때문이다. 다만 릴리스 모드에서는 최적화를 해주기에 큰 문제는 아니다.한편 반복문 안에서 벡터에 원소가 추가되거나 삭제되면 안된다. 왜냐하면 벡터의 내부 메모리가 해제될 가능성이 있기 때문이다. 벡터에 원소를 추가할 때 메모리 공간이 부족하면 새로 할당하는데, 이때 이전에 저장해놓은 원소를 새로운 메모리 공간으로 옮기고, 기존의 메모리 공간은 해제해버린다. 또한 원소를 삭제할 때는 삭제되는 원소 뒤에 있는 모든 원소를 앞으로 한칸 씩 당긴다. 이때 참조자가 원래 가리키던 원소에 빈 값이 들어갈 수도 있다. 문법 문서에서도 언급했지만 참조자는 하드 링크가 아니므로 가리키는 원소가 삭제되는 걸 감지할 수 없다. 이를 해결하려면 순회자(Iterator)와 함께 반복문을 써야 한다.
순회자, 또는 반복자라고 부르는 종류의 이 객체들은 C언어의 포인터의 작동 방식을 따와 메모리에 접근하는 방식을 추상화한 객체다. 표준 라이브러리에서는 <iterator> 모듈에서 관련 클래스와 유틸리티를 제공하고 있다.
순회자는 자료구조의 첫번째 원소의 위치를 가리키는 포인터, 마지막 원소의 다음 위치를 가리키는 포인터를 쓰기 좋게 구조화시킨 것이다.
C++ 표준의 모든 자료구조[2]는
begin()
, end()
, begin() const;
, end() const;
, cbegin() const;
, cend() const;
의 여섯가지 멤버 함수를 제공한다. 포인터를 쓰기 좋게 만든거라고 했는데 예제를 통해 한번 살펴보자.#!syntax cpp
std::vector<int> list{ 1, 2, 3, 4, 5 };
// (1) 원소 `1`을 가리키는 순회자
auto beg = list.begin();
// (2) `ref1`은 `int&`
auto& ref1 = *beg;
// (3) 원소 `2`를 가리키는 순회자
// `beg`를 진행시키고 반환함.
// 이때 `next`는 `beg`를 받아서 복사 생성됨.
auto next = ++beg;
// (4) 원소 `2`를 가리키는 순회자
// `next`를 진행시키고, 원래 `next`를 반환함.
auto nextagain = next++;
// (5) 원소 `4`를 가리키는 순회자
auto nextnext = next + 1;
상기 코드는 begin
을 써서 벡터의 원소를 참조하는 예시를 보여주고 있다. 연산자 오버로딩이 되어 있어서 포인터와 똑같이 사용할 수 있다. 대신 포인터의 포인터 처럼 포인터의 주소를 얻을 수는 없다. 이는 메모리 조작의 복잡성과 실수 가능성을 줄이고 안정성을 높힌다.#!syntax cpp
std::vector<int> list;
for (auto it = list.begin(); it != list.end(); ++it)
{
auto& value = *it;
// ...
}
반복문에서는 위와 같이 사용할 수 있다. end()
는 마지막 원소의 다음 위치를 가리키는 순회자를 반환한다. 이에 대해서는 후술한다. end()
가 반환한 순회자와 동등 비교를 통해 순회자가 자료구조의 마지막에 도달했는지 아닌지를 평가할 수 있다.#!syntax cpp
for (auto it = list.begin(); it < list.end(); ++it)
{
auto& value = *it;
// ...
}
참고로 배열, 벡터처럼 메모리 공간이 연속적인 자료구조는 순회자 클래스에 <
, >
, <=
, >=
~C++11 , <=>
C++20 연산자 오버로딩이 되어있다. 그래서 순회자끼리 비교 연산이 가능하다.다음으로는 포인터와 비교를 통해 왜
end()
가 요상한 반환값을 가지는지 알아보자.#!syntax cpp
std::vector<int> list{ 1, 2, 3, 4, 5 };
size_t cnt = list.size(); // 5
int* ptr = list.data();
// (1) 원소 `1`을 가리키는 포인터
int* ptr_beg = ptr;
// (2) 원소 `5` 다음의 메모리 위치를 가리키는 포인터
int* ptr_end = ptr + cnt;
// (3) 원소 `1`을 가리키는 순회자
auto beg = list.begin();
// (4) 원소 `5` 다음의 위치를 가리키는 순회자
auto end = list.end();
상기 코드는 begin
, end
와 이에 상응하는 포인터 선언을 보여주고 있다. 이를 반복문에서 써보자.#!syntax cpp
int array[5] { 1, 2, 3, 4, 5 };
int* end = array + 5;
for (int* beg = array.data(); beg != end; ++beg) { ... }
std::vector<int> list{ 1, 2, 3, 4, 5 };
auto end = list.end();
for (auto beg = list.begin(); beg != end; ++beg) { ... }
동등 비교 연산을 쓴 반복문은 위와 같이 구현된다. 모든 원소를 열거하려면 마지막 원소에 대해서도 조건문이 성공해야 한다. 여기서 만약 end
가 `5`
를 가리키는 포인터/순회자라면 어떻게 될까? 그럼 당연히 마지막 원소에서 반복문의 조건이 실패하고 실행되지 않는다. 즉 마지막 원소를 건너뛰어 버린다.#!syntax cpp
int array[5] { 1, 2, 3, 4, 5 };
for (int* beg = array.data(); beg < end; ++beg) { ... }
std::vector<int> list{ 1, 2, 3, 4, 5 };
for (auto beg = list.begin(); beg < end; ++beg) { ... }
비교 연산을 쓴 반복문은 위와 같이 구현된다.#!syntax cpp
// 가변 벡터
std::vector<int> list;
// (1) 불변 순회자
auto cbegin = list.cbegin();
// 불변 벡터
const std::vector<int> clist;
// (2) 불변 벡터는 항상 불변 순회자를 반환함.
auto con_begin = list.begin();
// 상동
auto con_cbegin = list.cbegin();
불변 벡터는 불변 순회자 인스턴스를 반환한다. 중요한 사실은 불변 순회자는 const std::vector::iterator
마냥 상수 인스턴스가 아니라, const_iterator
라는 별도의 클래스라는 것이다. 다행히도 불변 순회자와 가변 순회자는 연산자 오버로딩 덕분에 상호 변환, 대입, 동등 비교, 비교가 자유롭다. 그래서 자료형 한정자에 골치 썩힐 필요 없이 논리상으로 맞는 코드를 작성하면 항상 문제없다.2.3. 예제 3: 범위 for 반복문
범위 for 반복문은 일반적인 배열 외에vector
에도 사용할 수 있다. 사용법과 이론적 배경을 알아보자.#!syntax cpp
std::vector<int> list{ 10, 20, 30, 40, 50 };
int main()
{
// `10 20 30 40 50 ` 출력
for (int v : list)
{
std::print("{} ", v);
}
// 상동
// const int&로 명시해도 됨.
for (int& v : list)
{
std::print("{} ", v);
}
}
상기 코드는 아주 간단한 벡터 원소의 출력 예제를 보여주고 있다. 정수 벡터를 순회하면서 원소들을 출력한다. 범위 for 문에서 각 원소는 참조자로 반환된다. 그럼 이 동작이 어떻게 구현되는지 알아보자.#!syntax cpp
for (auto it = std::begin(range); it != std::end(range); (void)++it)
{
auto& v = *it;
// ...
}
범위 for 반복문은 내부적으로는 주어진 범위의 begin
, end
를 사용해서 열거한다.- <C++ 예제 보기>
#!syntax cpp namespace std { template<typename C> constexpr auto begin(C&& r) noexcept -> decltype(std::forward<C>(r).begin()) { return std::forward<C>(r).begin(); } template<typename C> constexpr auto end(C&& r) noexcept -> decltype(std::forward<C>(r).end()) { return std::forward<C>(r).end(); } template<typename T, size_t L> constexpr auto begin(T(&&array)[L]) noexcept { return array; } template<typename T, size_t L> constexpr auto end(T(&&array)[L]) noexcept { return array + L; } }
std::begin(r)
, std::end(r)
은 <iterator>
모듈 또는 각 컨테이너 모듈에 정의된 전역 함수다. 각각 컨테이너의 begin
, end
멤버 함수를 호출한다. 배열에 대해서도 오버로딩되어 있는데, 이때는 배열의 시작 주소와 마지막 원소 다음의 주소를 반환한다.2.4. 예제 4: 클래스의 vector
자명한 자료형 말고도 복잡한 클래스를 담는 벡터도 정의할 수 있다. 이때 벡터는 메모리를 할당할 때 원소의 기본 생성자를 호출한다는 점을 유의해야 한다. 해당 원소에 기본 생성자를 만들어주고, 기본 생성자를 호출하더라도 큰 문제가 없도록 구현해야 한다. 가령 운영체제 자원을 담는 클래스를 벡터에 담고 싶으면 이동 생성자와 소멸자를 필히 구현해줘야 할 것이다.#!syntax cpp
struct Squirrel
{
const char* myName;
int myAge;
bool isHungry;
bool isAlive;
};
std::vector<Squirrel> squirrels;
일단 자명한 클래스라면 사용할 때 문제가 없다. 복사, 이동, 성능, 소멸자 호출 여부 등등이 신경쓰일 상황이 없다. 그저 참조자나 const
정도나 가끔 신경써주면 문제없다. 대다수 어플리케이션 개발 단계에서는 이 정도로 문제없다.#!syntax cpp
struct Squirrel
{
std::string myName; // std::string은 자명하지 않은 자료형
int myAge;
bool isHungry;
bool isAlive;
};
std::vector<Squirrel> squirrels;
살짝 바꾼 클래스는 이럴건데 이름 데이터를 표준 라이브러리의 문자열 클래스로 바꿔줬다. 이 경우에도 성능 외에 별다른 문제는 발생하지 않는데, 왜냐하면 표준 라이브러리에서 동적 할당한 메모리를 해제하는 소멸자, 이동 생성자, 이동 대입 연산자를 구현하도록 하기 때문이다.#!syntax cpp
{
char* buf;
std::size_t size;
};
std::string
은 내부적으로 동적 할당된 char* buf
메모리를 들고 있으면서 소멸자에서 메모리를 해제한다. 또한 이동 생성자, 이동 대입 연산자에서는 자기가 가진 메모리와 이동 대상의 메모리를 교환(Swap)한다. 내부적으로는 std::swap(lhs.buf, rhs.buf)
또는 rhs.buf = std::exchange(lhs.buf, nullptr)
와 같이 구현된다.그런데 우리가 만약 동적 할당한 데이터를 담은 클래스를 벡터에 담고 싶다면? 마찬가지의 구현을 해줘야 한다.
- <C++ 예제 보기>
#!syntax cpp struct String { // 빈 문자열 constexpr String() noexcept = default; constexpr String(size_t length) : myBuffer(new char[length] {}), myLength(length) { } template<size_t Length> constexpr String(const char(&str)[Length]) : myBuffer(new char[Length] {}), myLength(Length) { std::copy(str, str + Length, myBuffer); } // 복사 생성자 constexpr String(const String& rhs) { if (rhs.myBuffer != nullptr and 0 < rhs.myLength) { myBuffer = new char[rhs.myLength] {}; myLength = rhs.myLength; std::copy(rhs.myBuffer, rhs.myBuffer + rhs.myLength, myBuffer); } } // 이동 생성자 constexpr String(String&& rhs) noexcept : myBuffer(std::exchange(rhs.myBuffer, nullptr)), myLength(std::move(rhs.myLength)) {} // 복사 대입 연산자 constexpr String& operator=(const String& rhs) { // 기존의 메모리 공간을 재활용할 수도 있음. if (myBuffer != nullptr) { myLength = 0; delete[] std::exchange(myBuffer, nullptr); } if (rhs.myBuffer != nullptr and 0 < rhs.myLength) { myBuffer = new char[rhs.myLength] {}; myLength = rhs.myLength; std::copy(rhs.myBuffer, rhs.myBuffer + rhs.myLength, myBuffer); } return *this; } // 이동 대입 연산자 constexpr String& operator=(String&& rhs) noexcept { if (myBuffer != nullptr) { myLength = 0; delete[] std::exchange(myBuffer, nullptr); } myBuffer = std::exchange(rhs.myBuffer, nullptr); myLength = std::move(rhs.myLength); return *this; } constexpr ~String() noexcept { if (myBuffer != nullptr) { myLength = 0; delete[] std::exchange(myBuffer, nullptr); } }; constexpr operator const char* () const noexcept { return myBuffer; }; char* myBuffer = nullptr; size_t myLength = 0; }; int main() { std::vector<String> strings(10, "Mutable"); const std::vector<String> cstrings(10, "Constant"); String element("Solo"); std::vector<String> strings_copy_initialized(10, element); strings_copy_initialized[2] = String("Move-Literal"); // `strings_copy_initialized[3]`의 버퍼가 해제되고, `element`의 버퍼는 그대로 옮겨감. strings_copy_initialized[3] = std::move(element); }
한편 상기 클래스
`String`
은 RAII 등의 예외 처리와 성능에 대한 고려가 없으므로 실제로 사용하기는 부적합하다.2.5. 예제 5: 알고리즘 라이브러리를 사용한 검색
STL이라고 칭하는 파트를 알아보자. 여기에선 순회자를 사용해 벡터의 원소를 찾는 방법을 확인해보자.#!syntax cpp
std::find(begin, end, value);
std::find_if(begin, end, predicate);
std::ranges::find(begin, end, value, [projection]);
std::ranges::find(r, value, [projection]);
std::ranges::find_if(begin, end, predicate, [projection]);
std::ranges::find_if(r, predicate, [projection]);
사용할 함수의 목록은 위와 같다.2.5.1. C++11의 알고리즘 응용 [1]
#!syntax cpp
std::vector<int> list{ 7, 14, 21, 28, 35, 49 };
// `found`는 원소 `21`을 가리키는 순회자
auto found = std::find(list.begin(), list.end(), 21);
// `notfound`는 `list`의 `end`
auto notfound = std::find(list.begin(), list.end(), 1);
상기 코드는 정수의 값을 찾는 예시를 보여주고 있다. 이때 내부적으로 값의 비교는 동등 비교 연산자(==
)로 수행한다. find
함수의 반환값은 해당 컨테이너의 순회자다. 만약 값을 찾지 못하면 컨테이너의 end()
를 반환한다. 컨테이너의 end()
또는 cend()
와 동등 비교해서 값을 찾는데 성공했는지 아닌지 여부를 알 수 있다.#!syntax cpp
std::vector<int> list{ 1, 2, 3, 4, 5 };
auto result0 = std::find(list.cbegin(), list.cend(), 2);
auto result1 = std::find(list.cbegin(), list.end(), 0);
auto result2 = std::find(list.begin(), list.cend(), 1);
find
는 불변 순회자를 써도 문제없다. 또한 불변 순회자와 가변 순회자를 섞어 써도 문제없이 동작한다.#!syntax cpp
std::vector<int> list1;
std::vector<int> list2;
// 런타임 오류!
std::find(list1.begin(), list2.end(), 0);
만약 두 순회자가 다른 컨테이너의 것이라면 런타임 오류가 발생한다. 컴파일 오류가 아니라서 실행할 때만 알 수 있으므로 주의해야 한다.
2.5.2. C++11의 알고리즘 응용 [2]
#!syntax cpp
std::vector<int> list{ 1, 3, 4, 5, 6, 7, 9 };
// (1) `found_even`은 `4`를 가리키는 순회자
auto found_even = std::find_if(list.cbegin(), list.cend(), [](const int& rhs) noexcept { return rhs % 2 == 0; });
bool seeker_sqr(const int& rhs) noexcept
{
return 50 <= rhs * rhs;
}
// (2) `found_sqr`은 `9`를 가리키는 순회자
auto found_sqr = std::find_if(list.cbegin(), list.cend(), seeker_sqr);
상기 코드는 `find_if`의 간단한 예시를 보여주고 있다. 각각 람다 표현식과 함수를 사용한 예제다. find_if
역시 컨테이너의 순회자를 반환한다. 사실 find_if
는 클래스와 함께 활용해야 진가가 빛난다.#!syntax cpp
struct Bldg
{
std::string name;
};
std::string favoriate_place_name = "Zoo";
std::vector<Bldg> places{ Bldg{ "Home" }, Bldg{ "School" }, Bldg{ "Restaurant" }, Bldg{ "Zoo" }, Bldg{ "Park" }, Bldg{ "Playground" } };
// `found`는 `Bldg{ "Zoo" }`를 가리키는 순회자
auto found = std::find_if(places.begin(), places.end(), [&](auto& bldg) { return bldg.name == favoriate_place_name; });
// `notfound`는 `places`의 `end`
auto notfound = std::find_if(places.begin(), places.end(), [](auto& bldg) { return bldg.name == "Aquarium"; });
상기 코드는 특정 이름을 가진 건물을 찾는 예시를 보여주고 있다.2.5.3. C++20의 범위 제약 알고리즘 응용
#!if 리스트 == null
{{{#!html 이 문단}}}에 대한 내용은 {{{#!if 같은문서1 == null
[[C++/표준 라이브러리]] 문서{{{#!if (문단1 == null) == (앵커1 == null)
를}}}{{{#!if (문단1 == null) != (앵커1 == null)
의 }}}}}}{{{#!if 문단1 != null & 앵커1 == null
[[C++/표준 라이브러리#s-|]]번 문단을}}}{{{#!if 문단1 == null & 앵커1 != null
[[C++/표준 라이브러리#|]] 부분을}}}{{{#!if 설명2 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서2 == null
[[]] 문서{{{#!if (문단2 == null) == (앵커2 == null)
를}}}{{{#!if (문단2 == null) != (앵커2 == null)
의 }}}}}}{{{#!if 문단2 != null & 앵커2 == null
[[#s-|]]번 문단을}}}{{{#!if 문단2 == null & 앵커2 != null
[[#|]] 부분을}}}}}}{{{#!if 설명3 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서3 == null
[[]] 문서{{{#!if (문단3 == null) == (앵커3 == null)
를}}}{{{#!if (문단3 == null) != (앵커3 == null)
의 }}}}}}{{{#!if 문단3 != null & 앵커3 == null
[[#s-|]]번 문단을}}}{{{#!if 문단3 == null & 앵커3 != null
[[#|]] 부분을}}}}}}{{{#!if 설명4 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서4 == null
[[]] 문서{{{#!if (문단4 == null) == (앵커4 == null)
를}}}{{{#!if (문단4 == null) != (앵커4 == null)
의 }}}}}}{{{#!if 문단4 != null & 앵커4 == null
[[#s-|]]번 문단을}}}{{{#!if 문단4 == null & 앵커4 != null
[[#|]] 부분을}}}}}}{{{#!if 설명5 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서5 == null
[[]] 문서{{{#!if (문단5 == null) == (앵커5 == null)
를}}}{{{#!if (문단5 == null) != (앵커5 == null)
의 }}}}}}{{{#!if 문단5 != null & 앵커5 == null
[[#s-|]]번 문단을}}}{{{#!if 문단5 == null & 앵커5 != null
[[#|]] 부분을}}}}}}{{{#!if 설명6 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서6 == null
[[]] 문서{{{#!if (문단6 == null) == (앵커6 == null)
를}}}{{{#!if (문단6 == null) != (앵커6 == null)
의 }}}}}}{{{#!if 문단6 != null & 앵커6 == null
[[#s-|]]번 문단을}}}{{{#!if 문단6 == null & 앵커6 != null
[[#|]] 부분을}}}}}}{{{#!if 설명7 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서7 == null
[[]] 문서{{{#!if (문단7 == null) == (앵커7 == null)
를}}}{{{#!if (문단7 == null) != (앵커7 == null)
의 }}}}}}{{{#!if 문단7 != null & 앵커7 == null
[[#s-|]]번 문단을}}}{{{#!if 문단7 == null & 앵커7 != null
[[#|]] 부분을}}}}}}{{{#!if 설명8 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서8 == null
[[]] 문서{{{#!if (문단8 == null) == (앵커8 == null)
를}}}{{{#!if (문단8 == null) != (앵커8 == null)
의 }}}}}}{{{#!if 문단8 != null & 앵커8 == null
[[#s-|]]번 문단을}}}{{{#!if 문단8 == null & 앵커8 != null
[[#|]] 부분을}}}}}}{{{#!if 설명9 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서9 == null
[[]] 문서{{{#!if (문단9 == null) == (앵커9 == null)
를}}}{{{#!if (문단9 == null) != (앵커9 == null)
의 }}}}}}{{{#!if 문단9 != null & 앵커9 == null
[[#s-|]]번 문단을}}}{{{#!if 문단9 == null & 앵커9 != null
[[#|]] 부분을}}}}}}{{{#!if 설명10 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서10 == null
[[]] 문서{{{#!if (문단10 == null) == (앵커10 == null)
를}}}{{{#!if (문단10 == null) != (앵커10 == null)
의 }}}}}}{{{#!if 문단10 != null & 앵커10 == null
[[#s-|]]번 문단을}}}{{{#!if 문단10 == null & 앵커10 != null
[[#|]] 부분을}}}}}}
#!if 리스트 != null
한시적 넘겨주기로 연결되는 다른 문서에 대한 내용은 아래 문서를
참고하십시오.#!if 리스트 != null
{{{#!if 문서명1 != null
* {{{#!if 설명1 != null
이 문단: }}}{{{#!if 같은문서1 == null
[[C++/표준 라이브러리]] {{{#!if (문단1 == null) != (앵커1 == null)
문서의 }}}}}}{{{#!if 문단1 != null & 앵커1 == null
[[C++/표준 라이브러리#s-|]]번 문단}}}{{{#!if 문단1 == null & 앵커1 != null
[[C++/표준 라이브러리#|]] 부분}}}}}}{{{#!if 문서명2 != null
* {{{#!if 설명2 != null
: }}}{{{#!if 같은문서2 == null
[[]] {{{#!if (문단2 == null) != (앵커2 == null)
문서의 }}}}}}{{{#!if 문단2 != null & 앵커2 == null
[[#s-|]]번 문단}}}{{{#!if 문단2 == null & 앵커2 != null
[[#|]] 부분}}}}}}{{{#!if 문서명3 != null
* {{{#!if 설명3 != null
: }}}{{{#!if 같은문서3 == null
[[]] {{{#!if (문단3 == null) != (앵커3 == null)
문서의 }}}}}}{{{#!if 문단3 != null & 앵커3 == null
[[#s-|]]번 문단}}}{{{#!if 문단3 == null & 앵커3 != null
[[#|]] 부분}}}}}}{{{#!if 문서명4 != null
* {{{#!if 설명4 != null
: }}}{{{#!if 같은문서4 == null
[[]] {{{#!if (문단4 == null) != (앵커4 == null)
문서의 }}}}}}{{{#!if 문단4 != null & 앵커4 == null
[[#s-|]]번 문단}}}{{{#!if 문단4 == null & 앵커4 != null
[[#|]] 부분}}}}}}{{{#!if 문서명5 != null
* {{{#!if 설명5 != null
: }}}{{{#!if 같은문서5 == null
[[]] {{{#!if (문단5 == null) != (앵커5 == null)
문서의 }}}}}}{{{#!if 문단5 != null & 앵커5 == null
[[#s-|]]번 문단}}}{{{#!if 문단5 == null & 앵커5 != null
[[#|]] 부분}}}}}}{{{#!if 문서명6 != null
* {{{#!if 설명6 != null
: }}}{{{#!if 같은문서6 == null
[[]] {{{#!if (문단6 == null) != (앵커6 == null)
문서의 }}}}}}{{{#!if 문단6 != null & 앵커6 == null
[[#s-|]]번 문단}}}{{{#!if 문단6 == null & 앵커6 != null
[[#|]] 부분}}}}}}{{{#!if 문서명7 != null
* {{{#!if 설명7 != null
: }}}{{{#!if 같은문서7 == null
[[]] {{{#!if (문단7 == null) != (앵커7 == null)
문서의 }}}}}}{{{#!if 문단7 != null & 앵커7 == null
[[#s-|]]번 문단}}}{{{#!if 문단7 == null & 앵커7 != null
[[#|]] 부분}}}}}}{{{#!if 문서명8 != null
* {{{#!if 설명8 != null
: }}}{{{#!if 같은문서8 == null
[[]] {{{#!if (문단8 == null) != (앵커8 == null)
문서의 }}}}}}{{{#!if 문단8 != null & 앵커8 == null
[[#s-|]]번 문단}}}{{{#!if 문단8 == null & 앵커8 != null
[[#|]] 부분}}}}}}{{{#!if 문서명9 != null
* {{{#!if 설명9 != null
: }}}{{{#!if 같은문서9 == null
[[]] {{{#!if (문단9 == null) != (앵커9 == null)
문서의 }}}}}}{{{#!if 문단9 != null & 앵커9 == null
[[#s-|]]번 문단}}}{{{#!if 문단9 == null & 앵커9 != null
[[#|]] 부분}}}}}}{{{#!if 문서명10 != null
* {{{#!if 설명10 != null
: }}}{{{#!if 같은문서10 == null
[[]] {{{#!if (문단10 == null) != (앵커10 == null)
문서의 }}}}}}{{{#!if 문단10 != null & 앵커10 == null
[[#s-|]]번 문단}}}{{{#!if 문단10 == null & 앵커10 != null
[[#|]] 부분}}}}}}
범위 제약 알고리즘 (Range-constrained algorithms)
C++20
에서 범위 제약 알고리즘이 도입되었다. 이유인 즉슨 기존의 알고리즘 함수는 잘못된 인자를 거를 수 없었고, 인자 의존성 검색과 파편화된 구현 때문에 함수 하나 쓰는데 활용 외적인 문제가 너무 많아서 이를 확실하게 대체할 수 있는 안전한 함자(Functor, Function Object, Function-like Object)를 도입한 것이다. 일단 이론적 배경은 알고리즘 모듈 문서에서 설명하고, 여기서는 예제만 설명하겠다.#!syntax cpp
std::vector list{ 20, 40, 60, 80 };
auto found = std::ranges::find(list.begin(), list.end(), 40);
auto notfound = std::ranges::find(list.begin(), list.end(), 50);
std::ranges::find
는 두가지 오버로딩이 있다. 순회자 두개를 받는 경우, 범위 컨테이너 하나만 받는 경우가 있다. 첫번째로 순회자를 받는 경우는 기존의 std::find
와 똑같은 방식으로 사용할 수 있다. 다른 점은 이 함수에는 템플릿 제약조건이 떡칠되어 있어서 잘못된 인자를 넣는 경우를 원천차단한다는 것이다.#!syntax cpp
struct FakeContainer
{
long begin();
int end();
};
FakeContainer container;
// 컴파일 오류!
std::ranges::find(container.begin(), container.end(), 10);
가령 위와 같이 적법한 순회자를 쓰지 않으면 컴파일 오류가 발생한다.#!syntax cpp
struct IntContainer
{
int* begin();
int* end();
};
IntContainer container;
// 컴파일 오류!
std::ranges::find(container.begin(), container.end(), "Takedown");
설령 적절한 순회자[3]를 반환하더라도, 찾는 값의 자료형이 다르면 컴파일 오류를 발생시킨다.#!syntax cpp
// std::vector<int>
std::vector list{ 7000, 5000, 6000, 8000, 10000, 9000, 1000 };
auto found = std::ranges::find(list, 8000);
auto notfound = std::ranges::find(list, 3000);
상기 코드는 범위 하나만을 인자로 받는 std::ranges::find
의 예시를 보여주고 있다. std::ranges::find(R&& range, const T& value)
에 걸린 제약조건이 무엇인지 알아보자. value
는 우리가 찾을 값이므로 알아서 넣으면 되지만 매개변수 range
는 조금 까다롭다. 바로 자료형 R
이 std::ranges::borrowed_range<R>
을 만족하지 않으면, 이 함수는 std::dangling
이라는 순회자를 찾을 수 없음을 나타내는 자료형을 반환한다. [4]왜 이런식으로 표준이 제정되었는지 설명해보자면 제약조건을 만족하지 않는다고 컴파일 오류를 띄우는 것도 나쁘진 않으나, 사용자가 오류의 원인을 찾으려[5] 오류 사례를 찾아보게 하는 대신에,
std::dangling
과 컨테이너의 순회자[6]를 비교하면 컴파일 오류가 발생하므로, 문제가 발생한 부분에서만 컴파일 오류를 띄우기 위함이다.- <C++ 예제 보기>
#!syntax cpp namespace std::ranges { template<class R> concept borrowed_range = ranges::range<R> && (std::is_lvalue_reference_v<R> || ranges::enable_borrowed_range<std::remove_cvref_t<R>>); template<class R> constexpr bool enable_borrowed_range = false; }
std::ranges::borrowed_range<R>
은 `R`
이 범위[7]이며, 동시에 lvalue
혹은 변수 템플릿 std::ranges::enable_borrowed_range<R>
가 true
임을 요구한다. 이 개념의 목적은 컨테이너 인스턴스의 수명을 보장해서 해당 컨테이너가 순회자에서 항상 어떤 값을 가져올 수 있도록 하고, 참조 대상 소실(Reference Dangling)을 미연에 차단하기 위함이다. 그래서 rvalue
는 사용하지 못해서 std::ranges::find(std::vector{ 1, 2, 3, 4, 5 }, 3);
와 같은 사용은 불가능하다. Rust 보다는 부족하나 사용자가 신경 쓸 부분을 오로지 컨테이너가 임시값이냐 아니냐로 축소한 것은 엄청 큰 개선점이다.표준에서 이를 만족하는 경우는 배열[(1)], 또는 멤버 함수로
begin
과 end
를 갖고 있으며, begin
과 end
가 적법한 순회자를 반환하고, begin
과 end
가 반환하는 순회자는 서로 동등 비교가 가능하며, 그 순회자는 ++
연산자로 전진(Forward)이 가능하고, *
연산자로 어떤 참조자를 반환하며, 그 참조자는 완성된 클래스[(2)]여야 한다. std::string
, std::string_view
등의 문자열 클래스도 만족한다.#!syntax cpp
// std::vector<int>
std::vector list{ 2, 4, 6, 8, 10, 11, 12, 14 };
auto found_odd = std::ranges::find_if(list.cbegin(), list.cend(), [](const int& rhs) noexcept { return rhs % 2 != 0; });
상기 코드는 두 순회자를 인자로 받는 std::ranges::find_if
의 예시를 보여주고 있다. 이 오버로딩은 사실 우리가 볼 게 없다.#!syntax cpp
// std::vector<float>
std::vector list{ 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f };
// `found`는 `0.5f`를 가리키는 순회자
auto found = std::ranges::find_if(list, [](auto& rhs) { return 1.0f <= std::round(rhs); });
상기 코드는 반올림한 부동 소수점 숫자를 찾는 예시를 보여주고 있다.#!syntax cpp
struct Vector { float x, y; };
std::vector<Vector> list;
auto it = std::ranges::find_if(list.begin(), list.end()
, [](auto& mag) noexcept { 30.0f <= mag; } // `mag`는 `projection`이 반환한 float 값
, [](auto& rhs) -> float { return std::sqrt(rhs.x * rhs.x + rhs.y * rhs.y); }); // `rhs`는 구조체 `Vector`
마지막으로 살펴볼 것은 선택적 매개변수인 [projection]
이다. projection
은 지정한 값을 다른 값으로 치환하는 역할을 한다. 함수, 람다 표현식 등의 함자 클래스를 전달할 수 있다. 그리고 비교 함수에는 projection
으로 치환한 값이 들어온다. 상기 코드에서는 구조체 Vector
를 단일한 실수 값으로 변환하고 있다. 이 함수를 쓰면 비교 함수를 복잡하게 만들 필요도 적어지고, 코드를 알아보기가 매우 쉬워진다. 코드의 길이가 늘어나는 건 어쩔 수 없다.2.6. 예제 6: 값의 삭제
||<-12><tablewidth=80%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>
||
벡터에서 원소를 삭제하는 방법을 알아보자. 삭제하는 방법은 벡터의 멤버 함수를 호출함으로써 이루어진다. 이를 돕기 위해 알고리즘 함수 #!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{constexpr}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{contexpr}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{void}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br]
#!if do_pre_linebreak = (pre_lnb != null)
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{pop_back}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br]
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}
||
벡터의 맨 뒤쪽에 있는 원소를 하나 제거한다. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
지정한 순회자 혹은 순회자 범위 내의 원소를 모두 삭제한다. 그러나 할당된 메모리 공간은 줄이지 않는다. | |||||||||||
<bgcolor=#20b580> |
remove
, remove_if
가 제공된다.#!syntax cpp
std::remove(begin, end, value);
std::remove_if(begin, end, predicate);
std::ranges::remove(begin, end, value, [projection]);
std::ranges::remove(r, value, [projection]);
std::ranges::remove_if(begin, end, predicate, [projection]);
std::ranges::remove_if(r, predicate, [projection]);
사용할 함수의 목록은 위와 같다.#!syntax cpp
std::vector<int> list{ 1, 2, 3, 4, 5 };
// list == { 1, 2, 3, 4 }
list.pop_back();
// `last`의 값은 4
auto last = list.last();
벡터의 멤버 함수 pop_back()
은 벡터 맨 뒤의 원소를 삭제한다. 알아둘 것은 이 함수는 아무것도 반환하지 않는다. 만약 마지막 값을 쓰고 싶으면 미리 last()
멤버 함수 등으로 마지막 원소를 가져와야 한다.#!syntax cpp
std::vector<std::string> ppg{ "Blossom", "Buttercup", "Bubbles" };
// `value`는 std::string("Bubbles")
auto value = std::move(ppg.last());
// ppg == { std::string("Blossom"), std::string("Buttercup") }
ppg.pop_back();
만약 동적 할당한 자원이 들어있거나 성능 문제가 있다면 이동 연산으로 마지막 원소를 빼내오면 된다.#!syntax cpp
std::vector<std::string> kdh{ "Rumi", "Mira", "Zoey" };
// `halfdemon`은 std::string("Rumi")를 가리키는 순회자
auto halfdemon = kdh.begin();
// kdh == { std::string("Mira"), std::string("Zoey") }
kdh.erase(halfdemon);
벡터의 멤버 함수 erase
는 두가지 오버로딩이 있다. 첫번째 오버로딩은 순회자 하나를 받는 경우다. 이 오버로딩은 벡터의 원소 하나를 삭제한다. 만약 지정한 벡터의 순회자가 아니면 런타임 오류가 발생하므로 주의해야 한다.#!syntax cpp
std::vector<std::string> sajaboys{ "Jinu", "Abby", "Baby", "Mistery", "Romance" };
// `next`는 string("Abby")를 가리키는 순회자
auto next = sajaboys.erase(sajaboys.begin());
// `sajaboys`의 모든 원소를 삭제한다.
for (; next != sajaboys.end(); ++next)
{
next = sajaboys.erase(next);
}
erase
는 삭제된 원소 다음의 원소를 가리키는 순회자를 반환한다. 중요한 사실은, 벡터 내부에서 메모리 할당 및 뒷부분 원소 당기기 등의 작업을 완료하고 나서 순회자를 반환한다는 것이다. 그래서 우리가 반환된 순회자를 쓸 때 참조 대상 소실의 걱정이 없다. 덕분에 반복문 내부에서 원소를 삭제하는 동작이 가능하다.#!syntax cpp
std::vector<int> list{ 1, 2, 3, 4, 5 };
// 3, 4 삭제
// list == { 1, 2, 5 }
auto it = list.erase(list.begin() + 2, list.end() - 1);
// 2, 5 삭제
// list == { 1 }
list.erase(list.begin() + 1, list.end());
두번째 오버로딩은 순회자를 두개 받는 경우다. 이 경우 지정한 첫번째 순회자가 가리키는 원소부터 두번째 순회자가 가리키는 원소 이전의 원소까지 삭제한다.#!syntax cpp
std::vector<int> list{ 1, 2, 3, 4, 5 };
// (1) `list`의 원소 3을 `list`의 제일 끝으로 옮긴다.
// `it`은 `list`의 원소 3을 가리키는 순회자
// list == { 1, 2, 4, 5, 3 }
auto it = std::remove(list.begin(), list.end(), 3);
// (2) `list`에서 원소 3을 삭제한다.
// list == { 1, 2, 4, 5 }
list.erase(it, list.end());
표준 라이브러리 알고리즘 중 하나인 std::remove(begin, end, value)
는 지정한 범위에서 지정한 값과 일치하는 모든 값을 지정한 범위의 맨 끝으로 옮긴다. 그리고 옮긴 위치 다음의 순회자를 반환한다. 즉 원소를 삭제하고 나서 나타날 새로운 범위의 마지막 순회자를 반환하는 것이다.이걸 정리하자면
std::remove
는 특정 원소를 실제로 삭제하지는 않고 옮기기만 하며, 대신 실제로 삭제되었을 시의 마지막 경계를 반환한다. 이 마지막 경계는 순회자고, 이 순회자부터 컨테이너의 현재 마지막 경계(end()
순회자)까지를 멤버 함수 erase
로 삭제하면 컨테이너에서 원소를 삭제하는 작업이 가능하다. 상기 코드를 보는 것이 빠른 이해에 도움이 될 것이다.- <C++ 예제 보기>
#!syntax cpp std::vector<int> list{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 }; // (1) list == { 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 0, 0 } // `it`은 컨테이너에서 원소 0이 제거된 새로운 범위의 마지막 경계 순회자 auto it = std::remove(list.begin(), list.end(), 0); // list == { 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5 } list.erase(it, list.end()); // (2) list == { 1, 2, 3, 4, 5, 7, 8, 9, 1, 2, 3, 4, 5, 7, 8, 9, 1, 2, 3, 4, 5, 6, 6 } // `it`은 컨테이너에서 원소 6이 제거된 새로운 범위의 마지막 경계 순회자 it = std::remove(list.begin(), list.end(), 6); // list == { 1, 2, 3, 4, 5, 7, 8, 9, 1, 2, 3, 4, 5, 7, 8, 9, 1, 2, 3, 4, 5 } list.erase(it, list.end());
#!syntax cpp
struct Point { float x, y; };
std::vector<Point> list{ Point{ 0, 0 }, Point{ 0.5f, 10.0f }, Point{ -20.7f, 4.5f }, Point{ 0, 16.667f }, Point{ 41.3f, 8.1f } };
// 구조체 `Point`의 `x`, `y` 둘 다 0보다 큰 원소를 전부 끝으로 옮긴다.
// `it`은 `Point{ 0.5f, 10.0f }`을 가리키는 순회자
// list == { Point{ 0, 0 }, Point{ -20.7f, 4.5f }, Point{ 0, 16.667f }, Point{ 0.5f, 10.0f }, Point{ 41.3f, 8.1f } };
auto it = std::remove_if(list.begin(), list.end(), [](const Point& pt) { return 0 < pt.x and 0 < pt.y; });
상기 코드는 std::remove_if
를 사용한 예시를 보여주고 있다. 마지막 인자로 특정 값 대신 실행할 수 있는 객체(Invocable Object)를 전달하면 된다.이 인자는 불변 원소[10]를 받아서 실행할 수 있어야 한다. 가령 함수라면 매개변수가
(const T& value)
처럼 상수 lvalue
이거나, 아니면 (T value)
처럼 상수이든 아니든 상관없는 서명을 가져야 한다.2.6.1. C++20의 범위 제약 알고리즘 응용
#!syntax cpp
struct Grade { std::string name; };
std::vector list{ Grade{ "A+" }, Grade{ "C+" }, Grade{ "B0" }, Grade{ "B+" }, Grade{ "D0" }, Grade{ "A0" }, Grade{ "C0" } };
// "B+" == 10
constexpr int score_standard = 10;
// "B+" 미만의 점수를 판별하는 실행문
// list == { Grade{ "A+" }, Grade{ "B+" }, Grade{ "A0" }, Grade{ "C+" }, Grade{ "B0" }, Grade{ "D0" }, Grade{ "C0" } };
// `subrange`는 원소 `Grade{ "C+" }`와 `list`의 end() 사이의 범위를 참조하는 하위 범위 (Subrange)
auto subrange = std::ranges::remove_if(list
, [score_standard](int score) noexcept -> bool { return score_standard < score; }
, [](const Grade& grade) -> int { return (grade.name[0] - 'A') * 10 + ((grade.name[1] - 40) % 3) * ((grade.name[1] - 40) / 3); });
// list == { Grade{ "A+" }, Grade{ "B+" }, Grade{ "A0" } }
list.erase(subrange.begin(), subrange.end());
상기 코드는 std::ranges::remove_if
의 마지막 매개변수 [projection]
을 써서 구조체 `Grade`
를 정수로 변환하고 있다.std::ranges::remove
와 std::ranges::remove_if
의 경우 앞서 살펴본 사례와 같이 rvalue
를 쓰지 못한다는 사실만 기억하면 된다. 그런데 std::ranges::remove
와 std::ranges::remove_if
는 특별한 자료형을 반환한다. 바로 전달된 컨테이너의 하위 범위(Subrange)다. 이 하위 범위는 std::ranges::subrange<Iterator, Sentinel, Kind>
로 정의된 클래스 템플릿이다. 이 클래스는 템플릿 매개변수로 순회자 클래스를 받는다. 전달된 순회자의 종류에 따라 가지는 멤버 함수와 반환 값이 다르다.벡터의 경우
begin()
, end()
가 있는 범위(std::ranges::range
)이고, 크기(Size)가 있는 범위(std::ranges::sized_range
)이고, 전진형 순회자(Forward Iterator)를 가지고, 무작위 접근(Random Access)이 가능한, 연속적인 메모리 공간에 저장되는(Contiguous) 선형 컨테이너다. C++의 컨테이너에서 상상할 수 있는 모든 멤버 함수를 가지고 있으며 벡터에서 할 수 있는 일은 하위 범위 클래스에서도 똑같이 할 수 있다.어려운 말만 많이 나열했는데, 이 함수에 잘못된 인자를 전달하면
std::dangling
을, 제대로 된 인자를 전달하면 또다른 벡터를 반환한다고 이해하면 된다.한편
std::map
같은 비선형 컨테이너의 경우 하위 범위 클래스에 제약이 많다. 템플릿 제약조건을 써서 클래스의 내용이 결정되기에 특정 멤버 함수가 빠져있는 경우가 있다. 그래서 사용자가 직접 구현하다보면 하위 범위 클래스의 멤버가 텅텅 비는 일도 일어난다. 일반화 프로그래밍의 달인이 아니면 활용하기는 매우 까다로운 편이다.2.7. 예제 7: 값의 삽입
||<tablewidth=80%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>
||<width=10%>C++23||
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{constexpr}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{contexpr}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{void}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{system_clock }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br]
#!if do_pre_linebreak = (pre_lnb != null)
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{append_range}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{container-compatible-range}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{Range}}}}}}
#!if arg1_t_post!=null
{{{&&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{range}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br]
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}
||<width=10%>C++23||
지정한 범위를 벡터의 맨 뒤쪽에 연달아 추가한다. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| |||||||||||
| C++11 | ||||||||||
지정한 순회자의 뒤쪽에 요소를 하나 추가한다. const_iterator 는 해당 벡터의 순회자만 사용할 수 있다. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
| C++23 | ||||||||||
지정한 순회자의 뒤쪽에 요소를 여러 개 연달아 추가한다. const_iterator 는 해당 벡터의 순회자만 사용할 수 있다. | |||||||||||
<bgcolor=#20b580> |
2.8. 예제 8: 정렬
자료구조를 사용하는 큰 이유 중 하나는 다수의 정보를 알아보기 쉽게 재단하는 데에 있다. 알아보기 쉽게 재단한다는 건 보통 어떤 기준을 두고 정보를 분류하는 것이 첫번째 지상과제라고 말할 수 있다. 그리고 분류를 하기 위해서는 보통 그 기준에 따라 정보의 순서를 정하기 마련이고, 순서를 정하는 이론 중 하나가 정렬(Sorting)이다. 정렬은 원소의 크고 작음에 따라 원소의 순서를 변경한다.C++11 이전에는 복사로 수행했으나, C++11 이후 이동 연산이 도입되어 성능이 기하급수적으로 상승했다. 앞서 간단한 예제로 봤었지만, 문자열만 하더라도 동적할당 된 문자열 버퍼를 복사하는 일이 쉽지 않음은 명백하다.
#!syntax cpp
std::sort(begin, end, [comparator]);
std::ranges::sort(begin, end, [comparator], [projection]);
std::ranges::sort(r, [comparator], [projection]);
사용할 함수의 목록은 위와 같다.#!syntax cpp
std::vector<int> list{ 5, 7, 3, 9, 2, 1, 6, 4, 7, 3, 2, 0, 0, 1, 6, 1, 8 };
// list == { 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 5, 6, 6, 7, 7, 8, 9 }
std::sort(list.begin(), list.end());
sort
는 원본 컨테이너를 직접 수정한다.#!syntax cpp
std::vector<int> list;
// 런타임 오류!
std::sort(list.cbegin(), list.cend());
const std::vector<int> clist;
// 런타임 오류!
std::sort(clist.begin(), clist.end());
그래서 불변 순회자로는 실행할 수 없다. 단 보통 cend()
는 범위 비교에만 쓰이므로 사용가능할 수도 있다.#!syntax cpp
struct Complex
{
double real = 0.0;
double img = 0.0;
double absolute() const { return std::sqrt(real * real + img * img); }
friend bool operator<(const Complex& lhs, const Complex& rhs) noexcept
{
return lhs.absolute() < rhs.absolute();
}
};
상기 클래스를 벡터에 넣고 정렬해보자. 비교 연산자를 오버로딩을 해줬다.- <C++ 예제 보기>
#!syntax cpp std::vector<Complex> list; list.push_back(Complex(30.0, 5.0)); list.emplace_back(10.0, 7.0); list.emplace_back(20.0); list.emplace_back(4.0, 12.0); // (1) list == { Complex(30.0, 5.0), Complex(10.0, 7.0), Complex(20.0, 0.0), Complex(4.0, 12.0) } std::sort(list.begin(), list.end() - 1); // (2) list == { Complex(10.0, 7.0), Complex(20.0, 0.0), Complex(30.0, 5.0), Complex(4.0, 12.0) } std::sort(list.begin(), list.end()); // (3) list == { Complex(4.0, 12.0), Complex(10.0, 7.0), Complex(20.0, 0.0), Complex(30.0, 5.0) }
#!syntax cpp
// std::vector<float>
std::vector list{ 60.0f, 20.0f, 40.0f, 80.0f, 70.0f, 10.0f };
// list == { 10.0f, 20.0f, 40.0f, 60.0f, 70.0f, 80.0f }
std::ranges::sort(list);
std::ranges::sort
역시 앞서 소개했던 범위 제약 알고리즘 함수들과 같은 결로 사용할 수 있다. rvalue
를 쓰지 못하는 것만 기억하면 된다.2.9. 예제 9: 이진 탐색
정렬을 통해 자료구조를 재단했으면 이제 자료구조를 실제로 활용해야 한다. 활용이란건 보통 자료구조에 담긴 정보를 찾거나 가공하는 행위를 말한다. 먼저 살펴볼 것은 정보의 검색이다. 앞서find
, find_if
를 확인했으나 이 함수들은 선형 탐색을 수행하여 성능이 그렇게 좋지는 않다. 대신 정렬된 자료 구조는 이진 탐색을 사용할 수 있다. 값의 검색 속도가 엄청나게 빨라진다. C++ 코드 상에서는 벡터를 정렬하고 std::lower_bound
등의 함수로 수행할 수 있다.#!syntax cpp
std::lower_bound(begin, end, value, [comparator])
std::upper_bound(begin, end, value, [comparator])
std::binary_search(begin, end, value, [comparator])
std::ranges::lower_bound(begin, end, value, [comparator], [projection])
std::ranges::upper_bound(begin, end, value, [comparator], [projection])
std::ranges::binary_search(begin, end, value, [comparator], [projection])
std::ranges::lower_bound(r, value, [comparator], [projection])
std::ranges::upper_bound(r, value, [comparator], [projection])
std::ranges::binary_search(r, value, [comparator], [projection])
사용할 함수의 목록은 위와 같다.||<(><tablewidth=100%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>[include(틀:C++ 요소, pre1_t=ForwardIterator, body_f=lower_bound, arg1_t=ForwardIterator, arg1_param=iterator1, arg2_t=ForwardIterator, arg2_param=iterator2, arg3_kw=const, arg3_t=T, arg3_t_post=&, arg3_param=value, arg4_t=\[Comparator, arg4_param=comparator\], body_bopen=1, body_bclose=1, last=;)]||
std::lower_bound
는 지정한 범위 [
iterator1
, iterator2
)
에서 값 T
가 나타나는 하한선 원소를 반환하는 알고리즘 함수다. 여기서 하한선이란, 이진 탐색을 통해 값이 첫번째로 나타나는 순회자를 의미한다. 결론적으로 범위 안에 지정한 값이 들어있으면 그 순회자를 반환하고, 아니면 iterator2
순회자를 반환한다. 그런데 이 함수는 세번째 경우도 상정한다. 만약 찾으려는 값이 범위 바깥에 있더라도 반드시 값이 없다고 반환되는 게 아니다.#!syntax cpp
std::vector<int> list{ 1, 3, 4, 5 };
// `it`은 원소 3을 가리키는 순회자
auto it = std::lower_bound(list.begin(), list.end(), 2);
가령 std::lower_bound
로 숫자 2를 찾으려는데, 자료구조에 1, 3, 4, 5가 들어있다면 어떻게 될까? 그 경우 std::lower_bound
는 원소 3을 가리키는 순회자를 반환한다. 곧 찾으려는 값보다 큰 원소를 가리키는 순회자를 반환한다.#!syntax cpp
std::vector<int> list{ 1, 3, 4, 5 };
// `it`은 `list`의 end
auto it = std::lower_bound(list.begin(), list.end(), 8);
그리고 찾으려는 값이 가장 큰 원소 값보다 크면 마지막 순회자를 반환한다. 이 정도의 기능을 가진 함수가 std::find
따위보다 훨씬 빨라서 O(logN)
의 시간 밖에 안걸린다.다시 말하지만 이 함수는 정렬된 자료구조에서 실행하는 걸 전제로 함을 기억해야 한다.
#!syntax cpp
struct Cmp
{
bool operator()(const int& lhs, const int& rhs) noexcept
{
return lhs > rhs;
}
};
std::vector<int> list{ 20, 30, 40, 50, 60 };
// `it`은 원소 20을 가리키는 순회자
// 기본값인 std::less<T>를 쓰면 50을 가리킴.
auto it = std::lower_bound(list.begin(), list.end(), 45, Cmp{});
사용자 정의 비교기를 마지막 인자에 전달할 수도 있다.기본값은
std::less<T>
인데, ()
연산자가 오버로딩되어 두 원소를 <
로 비교하는 기능을 한다.- <C++ 예제 보기>
#!syntax cpp import <random>; import <vector>; import <iterator>; import <algorithm>; struct Squirrel { int age; }; int main() { std::random_device device; std::uniform_int_distribution age_distr(0, 100); std::default_random_engine engine(device()); std::vector<Squirrel> squirrels; squirrels.reserve(10); std::insert_iterator<std::vector<Squirrel>> inserter(squirrels, squirrels.begin()); // 10개의 Squirrel 원소 추가 for (int i = 0; i < 10; ++i) { // 각 Squirrel은 [0~100) 범위에서 임의의 데이터 멤버 `age`를 가짐. *inserter = Squirrel{ age_distr(engine) }; } // 정렬 auto it = std::ranges::sort(squirrels, std::ranges::less{}, [](const Squirrel& squirrel) noexcept { return squirrel.age; }); // (1) `age`가 정확하게 50인 Squirrel 원소가 있으면 해당 원소를 가리키는 순회자 반환 // (2) 모든 Squirrel 원소의 `age`가 50 미만이면 `squirrels`의 begin 반환 // (3) 모든 Squirrel 원소의 `age`가 50 초과면 `squirrels`의 end 반환 // (4) `age`가 정확하게 50인 Squirrel 원소가 없는데, 50 상하로 섞여있으면 50 다음으로 큰 원소를 가리키는 순회자 반환 auto mid = std::ranges::lower_bound(squirrels, 50, std::ranges::less{}, [](const Squirrel& squirrel) -> int { return squirrel.age; }); }
std::ranges::sort
와 std::ranges::lower_bound
을 활용하는 법을 보여주고 있다. 모두 마지막 선택적 매개변수 [projection]
을 사용했다. [projection]
은 구조체 `Squirrel`
의 데이터 멤버 `age`
를 반환하도록 해서 정수로 변환하는 기능을 한다.2.10. 예제 10: 메모리 할당
앞서 벡터는 동적 할당을 써서 원소를 저장한다고 언급했었다. 그럼 동적 할당한 메모리 공간은 원소 개수에 딱 맞게 할당되는 것일까? 정답은 아니요 다. 벡터는 내부적으로 항상 원소 개수보다 큰 크기를 넉넉하게 할당해놓는다. 보통 메모리 정렬에 맞춰서 가장 가까운 2의 배수 혹은 2의 제곱수 따위로 정해진다.#!syntax cpp
std::vector<int> list(10, 0);
auto end = list.end();
// 사용하면 안되는 코드이지만, 벡터가 내부적으로 할당한 메모리 크기는 보통 원소의 개수보다는 크므로 오류가 발생하지 않을 가능성이 크다.
// 런타임 오류! 메모리 참조 위반! -또는- 실행될 수도 있음.
// 이전에 list.shrink_to_fit()을 실행하면 내부 메모리 공간의 크기를 딱 맞게 줄이므로 메모리 참조 오류가 발생한다.
*end = 6;
가령 상기 코드처럼 end
이후의 메모리 위치를 참조할 수도 있다. 그러나 이는 C++에서 하면 안되는 9999가지 일 중 하나에 속하며 개중에서도 가장 치명적인 사례다. 보안 문제, 정의되지 않은 동작 발생 등 여러 문제를 일으킬 수 있다. Rust의 등장 연유가 이런 짓을 방지하려고 나온 것인만큼 우리도 엄수해야 한다. 반드시 if (it == list.end())
따위의 검사 구문을 첨부하고 사용해야 한다. 이것이 마음에 들지 않는다면 순회자(Iterator) 모델 대신에 C#이나 Java에 있는 열거자(Enumerator) 모델을 구현하거나 컨테이너를 감싸는 래퍼 클래스(Wrapper Class)를 구현해야 한다.
|
std::vector::clear
는 벡터 내부의 모든 원소의 소멸자를 호출하고 크기를 0으로 설정한다. 모든 원소를 삭제한 것으로 처리하지만 실제로 내부에는 할당된 메모리가 그대로 남아있다. 실제로 메모리가 삭제되는 시점은 벡터의 소멸자가 호출될 때다.
|
std::vector::empty
는 벡터에 원소가 들어있는지 여부를 반환한다. 정확히는 벡터의 크기가 0보다 큰지 여부를 반환한다.
|
std::vector::empty
는 벡터의 크기를 반환한다. 그러나 벡터 내부의 메모리 크기가 아니라 원소의 개수를 반환한다.
|
std::vector::capacity
는 벡터 내부의 메모리 크기를 반환한다. 여기서 메모리 크기란 바이트 크기가 아니라 저장할 수 있는 원소의 개수다.vector<T>.capacity() * sizeof(T)
따위의 구문으로 벡터의 바이트 크기를 알 수 있다.
|
std::vector::shrink_to_fit
는 벡터 내부의 메모리 크기를 현재 저장된 원소의 크기로 축소한다. 즉 현재 사용중이지 않은 메모리를 해제한다.그러나 이 함수를 빈번하게 실행하는 건 성능 하락을 불러일으킨다. 벡터도 엄연히 동적 할당을 사용하기 때문이다. 또한 새로운 원소를 추가할 때 메모리를 또 할당해야 한다. 그러므로 정의 후에 아예 수정하지 않을 벡터에서만 사용하고 그 외에는 거들떠도 보지 않는 게 좋다.
만약 벡터 인스턴스를 선언했을 때 또는
clear()
직후 등 들어있는 원소의 개수가 0이면 벡터의 내부 메모리가 완전히 해제된다.
|
std::vector::reserve
는 벡터의 내부 메모리를 최소한 new_capacity
의 크기 만큼 할당한다. 만약 벡터의 내부 메모리 크기가 이미 new_capacity
보다 크면 아무것도 안한다.
|
std::vector::resize(new_size)
는 벡터에 들어있는 원소 개수가 new_size
가 되도록 조정한다. 만약 벡터에 든 원소의 개수가 new_size
보다 크면, 초과된 개수만큼 원소를 삭제한다. 원소의 개수가 new_size
와 같으면 아무것도 안한다. 원소의 개수가 new_size
보다 작으면 새로운 원소를 뒤쪽에 추가한다. 이때 추가되는 원소는 기본 생성자를 호출한다.
|
std::vector::resize(new_size, value)
는 벡터에 들어있는 원소 개수가 new_size
가 되도록 조정하되 추가되는 원소는 value
로부터 복사해서 추가한다. 만약 벡터에 든 원소의 개수가 new_size
보다 크면, 초과된 개수만큼 원소를 삭제한다. 원소의 개수가 new_size
와 같으면 아무것도 안한다. 원소의 개수가 new_size
보다 작으면 새로운 원소를 뒤쪽에 추가한다. 이때 추가되는 원소는 value
를 복사하면서 복사 생성자를 호출한다.- <C++ 예제 보기>
#!syntax cpp #include <print> #include <vector> int main() { std::vector<int> v; // (1) "기본 적재공간의 크기는 0." 출력 std::println("기본 적재공간의 크기는 {}.", v.capacity()); // (2) "원소 100개가 든 벡터의 적재공간의 크기는 100." 출력 v.resize(100); std::println("원소 100개가 든 벡터의 적재공간의 크기는 {}.", v.capacity()); // (3) "크기를 원소 50개로 조정한 벡터의 적재공간의 크기는 50." 출력 v.resize(50); std::println("크기를 원소 50개로 조정한 벡터의 적재공간의 크기는 {}.", v.capacity()); // (4) "크기에 맞게 축소한 벡터의 적재공간의 크기는 50." 출력 v.shrink_to_fit(); std::println("크기에 맞게 축소한 벡터의 적재공간의 크기는 {}.", v.capacity()); // (5) "원소를 정리한 벡터의 적재공간의 크기는 50." 출력 v.clear(); std::println("원소를 정리한 벡터의 적재공간의 크기는 {}.", v.capacity()); // (6) "크기에 맞게 축소한 벡터의 적재공간의 크기는 0." 출력 v.shrink_to_fit(); std::println("크기에 맞게 축소한 벡터의 적재공간의 크기는 {}.", v.capacity()); // (7) "크기에 맞게 축소한 벡터의 적재공간의 크기는 512." 출력 // 참고로 빌드 환경과 컴파일러에 따라 다를 수 있음. for (int i = 1000; i < 1300; ++i) { v.push_back(i); } std::println("원소 300개를 추가한 벡터의 적재공간의 크기는 {}.", v.capacity()); // (8) "크기에 맞게 축소한 벡터의 적재공간의 크기는 300." 출력 v.shrink_to_fit(); std::println("크기에 맞게 축소한 벡터의 적재공간의 크기는 {}.", v.capacity()); }
shrink_to_fit의 예제를 가져왔다.
2.11. 예제 11: vector의 vector
우리는 가끔 2차 배열 등의 다차원 배열을 썼다. 벡터는 복잡해보이지만 결국 내부적으로는data[N]
을 추상화한 클래스이며 수많은 멤버 함수는 내부 포인터에 접근하는 수단(Method)일 뿐이다. 배열의 경우와 같이 벡터도 중첩되어 정의될 수 있다. vector<vector<T>>
따위의 구문이 가능하며 사용에 큰 문제도 없다.#!syntax cpp
std::vector<std::vector<int>> twodimensional_list;
다차원 벡터는 상기 코드와 같은 식으로 정의한다. 만약 더 깊은 배열이 필요하면 계속 벡터의 수를 늘려나가면 된다.#!syntax cpp
// 5개의 빈 std::vector<int>를 미리 할당
std::vector<std::vector<int>> twodimensional_list(5, {});
// 상동
std::vector<std::vector<int>> twodimensional_list(5, std::vector<int>{});
// `innerlist`의 자료형은 std::vector<int>
// `innerlist`는 빈 std::vector<int>
auto& innerlist = twodimensional_list[1];
// `copiedlist`는 복사 생성된 std::vector<int>
auto copiedlist = twodimensional_list[2];
// C++23의 append_range(R&& range)
// copiedlist == { 3, 5, 7 }
copiedlist.append_range({ 3, 5, 7 });
상기 코드는 다차원 벡터의 원소를 참조하는 예시를 보여주고 있다.2.12. 예제 12: 메모리 할당자
#!syntax cpp
struct MyAllocator
{
constexpr void* allocate(size_t count);
};
// vector(initializer_list<T>, Allocator = Allocator())
std::vector<int, MyAllocator> intlist{ 33, 44, 55 };
std::vector<int, MyAllocator> intlist({ 33, 44, 55 }, MyAllocator());
/* C++17부터 가능한 연역 선언 방식 */
std::vector intlist({ 33, 44, 55 }, MyAllocator());
마지막으로 살펴볼 것은 벡터의 마지막 템플릿 매개변수인 할당자(Allocator)다. 벡터의 두번째 템플릿 매개변수에는 할당자 클래스를 전달할 수 있다. 할당자는 벡터가 메모리를 할당하는 방식을 결정한다. 가령 새로운 원소가 생성될 때, 새로운 메모리 블록을 할당할 때, 메모리 블록을 해제할 때, 원소를 삭제할 때 등의 동작을 정의할 수 있다. 지금까지 벡터를 사용해왔다면 알 수 있듯이 기본적으로는 사용자가 클래스를 넣어줄 필요가 없다. std::allocator<T>
라는 클래스 템플릿이 기본값이다.원시 자료형에 대해서는 큰 효용은 없으나, 복잡한 구조체나 시스템 자원[11]을 저장할 때 활용할 수 있다. 이미 수십년간 쌓인 최적화를 이기는 구현을 만들기는 쉽지 않다.
할당자에 대하여 살펴보기 전에 할당자를 쓸만한 상황을 몇가지 알아보자.
#!syntax cpp
float map_raw_data[300]{};
auto map_raw_view_ptr = new(map_raw_data) std::vector<float>(300);
auto& map_raw_view = *map_raw_view_ptr;
struct Point3D { float x, y, z; };
Point3D* map_pointers = new(map_raw_data) Point3D[100]{};
auto map_points_view_ptr = new(map_pointers) std::vector<Point3D>(100);
auto& map_points_view = *map_points_view_ptr;
map_points_view[2] = Point3D{ 10, 80, 100 };
map_points_view[20] = Point3D{ 40, 90, 10 };
map_points_view[5] = Point3D{ 60, 20, 20 };
map_points_view[99] = Point3D{ 10, 50, 30 };
map_points_view[50] = Point3D{ 70, 0, 10 };
상기 코드는 300개의 실수를 관리하는 벡터를 보여주고 있다. new
를 사용하나 컴파일러에 따라 다르지만 동적할당을 수행하지 않는다. 왜냐하면 모든 객체가 `map_raw_data`
의 메모리 위치에서 생성되기 때문이다. 하지만 컴파일러에 따라 작동할 수도 안할 수도 있는 비표준 구문이다. 이런 식으로 메모리 관리를 통합하는 올바른 방법론은 할당자를 새로이 구현하는 것이다. ||<-12><tablewidth=80%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>
std::allocator<T>
||
| |||||||||||
<colcolor=#f0f0f8,#e1e1e1>표준 할당자 클래스 템플릿
| |||||||||||
<bgcolor=#20b580> | |||||||||||
| |||||||||||
자료형 T 가 count 개수만큼 적재될 수 있는 크기의 메모리를 할당한다. 만약 실패하면 예외가 발생하며 반환값은 nullptr 다. 중요한 사실은 이 함수는 메모리 할당만 해주고 메모리 초기화를 하지 않는다. 표준에서는 Uninitialized memory라고 표현한다. 그러므로 T 의 객체가 생성되지 않으며 즉 생성자를 호출하지 않는다. std::construct_at 혹은 *memory = T(); 등의 구문으로 직접 생성해야 한다. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| C++23 | ||||||||||
지정한 개수만큼의 객체가 들어갈 메모리를 할당하고, 이를 std::allocation_result<T, size_type> 라는 구조체에 담아 반환한다. 데이터 멤버 T* ptr 에는 할당한 메모리의 포인터, size_type count 에는 할당한 개수가 전달된다. 최소 개수이기 때문에 더 큰 객체 메모리가 할당될 수 있다. 허나 allocate 와 마찬가지로 메모리 초기화를 하지 않아 객체가 생성되지 않는다. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| |||||||||||
주소 ptr 부터 number 개수 만큼의 T 가 적재될 수 있는 크기의 메모리를 해제한다. 중요한 사실은 이 함수는 오로지 동적 할당한 메모리의 해제만 담당한다는 것이다. 즉 소멸자를 호출하지 않는다. std::destroy_at , std::destroy_n , std::destroy [12]를 쓰던가, 아니면 instance.~T(); 처럼 직접 호출해줘야 한다. |
상기 표의 멤버 함수와 멤버 자료형 별칭을 구현해주면 기본적인 할당자의 구색은 갖출 수 있다. 표준에 따르면
std::allocator_traits<Allocator>
라는 클래스도 내부적으로 연동되기 때문에 멤버 자료형 별칭도 선언해줘야 한다.#!syntax cpp
struct Point3D { float x, y, z; };
// 무조건 전역 변수일 필요는 없음.
std::array<float, 300> map_data;
// `MyPointAllocator`의 정의는 `map_data`와 같은 스코프에 있어야 한다.
class MyPointAllocator
{
public:
// 필수
using value_type = Point3D;
// MSVC에선 필수
// 다른 컴파일러에서는 없어도 됨.
template<typename U>
struct rebind
{
using other = MyPointAllocator;
};
MyPointAllocator() noexcept = default;
MyPointAllocator(const MyPointAllocator&) noexcept {}
~MyPointAllocator() noexcept = default;
Point3D* allocate(size_t count)
{
// 형변환 이외에 아무것도 안함.
return reinterpret_cast<Point3D*>(map_data.data());
}
void deallocate(Point3D* ptr, size_t count)
{
// 아무것도 안함.
}
};
int main()
{
std::vector<Point3D, MyPointAllocator> map_points_view(100);
map_points_view[2] = Point3D{ 10, 80, 100 };
map_points_view[30] = Point3D{ 40, 90, 10 };
map_points_view[7] = Point3D{ 60, 20, 20 };
map_points_view[99] = Point3D{ 10, 50, 30 };
map_points_view[50] = Point3D{ 70, 0, 10 };
auto ptr = map_data.data();
auto getter = [](size_t index, size_t memid) -> float&
{
return map_data[index * 3 + memid];
};
// `[2] = (10, 80, 100)` 출력
std::println("[2] = ({}, {}, {})", getter(2, 0), getter(2, 1), getter(2, 2));
// `[30] = (40, 90, 10)` 출력
std::println("[30] = ({}, {}, {})", getter(30, 0), getter(30, 1), getter(30, 2));
// `[7] = (60, 20, 20)` 출력
std::println("[7] = ({}, {}, {})", getter(7, 0), getter(7, 1), getter(7, 2));
// `[99] = (10, 50, 30)` 출력
std::println("[99] = ({}, {}, {})", getter(99, 0), getter(99, 1), getter(99, 2));
// `[50] = (70, 0, 10)` 출력
std::println("[50] = ({}, {}, {})", getter(50, 0), getter(50, 1), getter(50, 2));
}
구현 예시는 위와 같다. 핵심은 메모리를 공유하는, 우리가 추가한 할당자에서는 메모리 공간의 조작을 하면 안된다. 오로지 먼저 할당해놓은 공간을 참조하기만 할 뿐이다. 예제의 `MyPointAllocator`는 멤버 함수 allocate
와 deallocate
에서 아무것도 안한다. 이렇게 하면 메모리의 중복 할당, 중복 해제가 일어나지 않는다.==# 코드 요약 #==
#!syntax cpp
#include <compare>
#include <initializer_list>
namespace std
{
// class template vector
template<class T, class Allocator = allocator<T>>
class vector;
// specialization of vector for bool
// partial class template specialization vector<bool, Allocator>
template<class Allocator>
class vector<bool, Allocator>;
namespace pmr
{
template<class T>
using vector = std::vector<T, polymorphic_allocator<T>>;
}
// erasure
template<class T, class Allocator, class U = T>
constexpr typename vector<T, Allocator>::size_type erase(vector<T, Allocator>& c, const U& value);
template<class T, class Allocator, class Predicate>
constexpr typename vector<T, Allocator>::size_type erase_if(vector<T, Allocator>& c, Predicate pred);
template<class T, class Allocator>
constexpr void swap(vector<T, Allocator>& x, vector<T, Allocator>& y) noexcept(noexcept(x.swap(y)));
template<class T, class Allocator>
constexpr bool operator==(const vector<T, Allocator>& x, const vector<T, Allocator>& y);
template<class T, class Allocator>
constexpr /*synth-three-way-result*/<T> operator<=>(const vector<T, Allocator>& x, const vector<T, Allocator>& y);
// hash support
template<class T>
struct hash;
template<class Allocator>
struct hash<vector<bool, Allocator>>;
// formatter specialization for vector<bool>
template<class T, class CharT>
requires /*is-vector-bool-reference*/<T>
struct formatter<T, CharT>;
template<class T, class Allocator = allocator<T>>
class vector
{
public:
// types
using value_type = T;
using allocator_type = Allocator;
using pointer = typename allocator_traits<Allocator>::pointer;
using const_pointer = typename allocator_traits<Allocator>::const_pointer;
using reference = value_type&;
using const_reference = const value_type&;
using size_type = /* implementation-defined */;
using difference_type = /* implementation-defined */;
using iterator = /* implementation-defined */;
using const_iterator = /* implementation-defined */;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// construct/copy/destroy
constexpr vector() noexcept(noexcept(Allocator()))
: vector(Allocator())
{}
constexpr explicit vector(const Allocator&) noexcept;
constexpr explicit vector(size_type n, const Allocator& = Allocator());
constexpr vector(size_type n, const T& value, const Allocator& = Allocator());
template<class InputIter>
constexpr vector(InputIter first, InputIter last, const Allocator& = Allocator());
template<container-compatible-range<T> R>
constexpr vector(from_range_t, R&& rg, const Allocator& = Allocator());
constexpr vector(const vector& x);
constexpr vector(vector&&) noexcept;
constexpr vector(const vector&, const type_identity_t<Allocator>&);
constexpr vector(vector&&, const type_identity_t<Allocator>&);
constexpr vector(initializer_list<T>, const Allocator& = Allocator());
constexpr ~vector();
constexpr vector& operator=(const vector& x);
constexpr vector& operator=(vector&& x) noexcept(allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||allocator_traits<Allocator>::is_always_equal::value);
constexpr vector& operator=(initializer_list<T>);
template<class InputIter>
constexpr void assign(InputIter first, InputIter last);
template<container-compatible-range<T> R>
constexpr void assign_range(R&& rg);
constexpr void assign(size_type n, const T& u);
constexpr void assign(initializer_list<T>);
constexpr allocator_type get_allocator() const noexcept;
// iterators
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
constexpr reverse_iterator rbegin() noexcept;
constexpr const_reverse_iterator rbegin() const noexcept;
constexpr reverse_iterator rend() noexcept;
constexpr const_reverse_iterator rend() const noexcept;
constexpr const_iterator cbegin() const noexcept;
constexpr const_iterator cend() const noexcept;
constexpr const_reverse_iterator crbegin() const noexcept;
constexpr const_reverse_iterator crend() const noexcept;
// capacity
constexpr bool empty() const noexcept;
constexpr size_type size() const noexcept;
constexpr size_type max_size() const noexcept;
constexpr size_type capacity() const noexcept;
constexpr void resize(size_type sz);
constexpr void resize(size_type sz, const T& c);
constexpr void reserve(size_type n);
constexpr void shrink_to_fit();
// element access
constexpr reference operator[](size_type n);
constexpr const_reference operator[](size_type n) const;
constexpr const_reference at(size_type n) const;
constexpr reference at(size_type n);
constexpr reference front();
constexpr const_reference front() const;
constexpr reference back();
constexpr const_reference back() const;
// data access
constexpr T* data() noexcept;
constexpr const T* data() const noexcept;
// modifiers
template<class... Args>
constexpr reference emplace_back(Args&&... args);
constexpr void push_back(const T& x);
constexpr void push_back(T&& x);
template<container-compatible-range<T> R>
constexpr void append_range(R&& rg);
constexpr void pop_back();
template<class... Args>
constexpr iterator emplace(const_iterator position, Args&&... args);
constexpr iterator insert(const_iterator position, const T& x);
constexpr iterator insert(const_iterator position, T&& x);
constexpr iterator insert(const_iterator position, size_type n, const T& x);
template<class InputIter>
constexpr iterator insert(const_iterator position, InputIter first, InputIter last);
template<container-compatible-range<T> R>
constexpr iterator insert_range(const_iterator position, R&& rg);
constexpr iterator insert(const_iterator position, initializer_list<T> il);
constexpr iterator erase(const_iterator position);
constexpr iterator erase(const_iterator first, const_iterator last);
constexpr void swap(vector&) noexcept(allocator_traits<Allocator>::propagate_on_container_swap::value || allocator_traits<Allocator>::is_always_equal::value);
constexpr void clear() noexcept;
};
template<class InputIter, class Allocator = allocator</*iter-value-type*/<InputIter>>>
vector(InputIter, InputIter, Allocator = Allocator()) -> vector</*iter-value-type*/<InputIter>, Allocator>;
template<ranges::input_range R, class Allocator = allocator<ranges::range_value_t<R>>>
vector(from_range_t, R&&, Allocator = Allocator()) -> vector<ranges::range_value_t<R>, Allocator>;
}
3. 둘러보기
||<:><-12><width=90%><tablewidth=80%><tablebordercolor=#20b580><rowbgcolor=#090f0a,#050b09><rowcolor=#d7d7d7,#a1a1a1>C++||||
}}}
}}}||
}}}||
C언어와의 차이점 | 학습 자료 | 평가 | |||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>C++ 문법 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
main | 헤더 | 모듈 | |||||||||||||
함수 | 구조체 | 이름공간 | |||||||||||||
한정자 | 참조자 | 포인터 | |||||||||||||
클래스 | 값 범주론 | 특성 | |||||||||||||
auto | using | decltype | |||||||||||||
상수 표현식 | 람다 표현식 | 객체 이름 검색 | |||||||||||||
템플릿 | 템플릿 제약조건 | 메타 프로그래밍 | |||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>C++ 버전 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
C++26 | C++23 | C++20 | |||||||||||||
C++17 | C++14 | C++11 | |||||||||||||
C++03 | C++98 | C with Classes | |||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>C++ 표준 라이브러리 | |||||||||||||||
<rowcolor=#090912,#bebebf>문서가 있는 모듈 목록 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
#개요 | C++11 #개요 | <unordered_map> C++11 #개요 | |||||||||||||
C++20 #개요 | #개요 | #개요 | |||||||||||||
C++11 #개요 | C++11 #개요 | C++17 #개요 | |||||||||||||
#개요 | <string_view> C++17 #개요 | C++20 #개요 | |||||||||||||
C++11 #개요 | C++11 #개요 | C++11 #개요 | |||||||||||||
C++20 #개요 | C++23 #개요 | ||||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>예제 목록 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
{{{#!wiki style=""text-align: center, margin: 0 -10px" {{{#!folding [ 펼치기 · 접기 ] | 동기화 전략 1 임계 영역과 상호 배제 | 동기화 전략 2 원자적 연산과 메모리 장벽 | 동시성 자료 구조 1 큐 구현 | 동시성 자료 구조 2 집합 구현 | |||||||||||
함수 템플릿 일반화 프로그래밍 | 전이 참조 완벽한 매개변수 전달 | 튜플 구현 가변 클래스 템플릿 | 직렬화 함수 구현 템플릿 매개변수 묶음 | ||||||||||||
SFINAE 1 멤버 함수 검사 | SFINAE 2 자료형 태그 검사 | SFINAE 3 메타 데이터 | SFINAE 4 자료형 트레잇 | ||||||||||||
제약조건 1 개념 (Concept) | 제약조건 2 상속 여부 검사 | 제약조건 3 클래스 명세 파헤치기 | 제약조건 4 튜플로 함자 실행하기 | ||||||||||||
메타 프로그래밍 1 특수화 여부 검사 | 메타 프로그래밍 2 컴파일 시점 문자열 | 메타 프로그래밍 3 자료형 리스트 | 메타 프로그래밍 4 안전한 union |
}}}||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>외부 링크 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
{{{#!wiki style=""text-align: center, margin: 0 -10px" | |||||||||||||||
<bgcolor=#20b580> | |||||||||||
<rowcolor=#090912,#bebebf>C++ |
[1] 이하 '벡터'[2] std::optional, std::generator 제외[3] 상기 예제에서는 포인터[4] Dangling은 참조 대상 소실(Reference Dangling)에서 알 수 있듯 참조할 대상이 소실됐다는 의미다[5] 앞서 소개하기도 한[6] 가령 벡터의 end[7] 멤버 함수 begin, end를 갖고 있으며, begin, end가 적법한 순회자를 반환하는 클래스[(1)] [(2)] [10] 즉 상수 객체[11] 스레드 등[12] 연속된 메모리 공간 해제용도