알고리즘이 추상 데이터 유형을 처리할 수 있는 까닭은 템플릿(template)에 기반하고 있기 때문이다. 이 장에서는 템플릿의 생성은 다루지 않는다 (생성에 관해서는 제 21장 참고). 그보다는 알고리즘의 사용법에 초점이 있다.
표준 템플릿 라이브리에서 사용되는 여러 요소들을 이미 다루어 보았다. 제 12장에 컨테이너를 다루었고 11.10절에 함수객체를 소개했다. 또한 반복자도 여러 곳에 언급한 바가 있다.
STL의 주요 구성요소를 이 장과 다음 장에 다룬다. 다음 절부터 STL의 반복자, 어댑터, 스마트 포인터, 다중 쓰레드 그리고 기타 특징들을 다룬다. 총칭 알고리즘은 다음 장에 다룬다 (제 19장).
배당자(Allocator)는 STL에서 메모리 배당을 관리한다. 기본 배당자 만으로도 대부분의 어플리케이션을 만족시키므로 더 깊게 다루지 않는다.
STL의 모든 요소들은 표준 이름공간에 정의되어 있다. 그러므로 using namespace std
또는 그 비슷한 지시어가 필요하다. 물론 이름공간을 명시적으로 지정하고 싶지 않다면 상관이 없다. 헤더 파일에 std
이름공간을 명시적으로 지정해야 한다 (7.11.1항).
이 장은 빈 옆꺽쇠 표기법을 자주 사용한다. 코드에서 유형이름은 옆꺽쇠 사이에 놓아야 한다. 이 책에서는 plus<>
표기법을 사용하더라도 실전 코드에서는 plus<string>
형태를 만날 것이다.
<functional>
헤더를 포함해야 한다.
함수객체는 총칭 알고리즘에서 중요한 역할을 한다. 예를 들어 sort
총칭 알고리즘은 두 개의 반복자를 기대한다. 하나는 정렬될 객체의 범위이고 또 하나는 비교 연산자를 호출하는 함수객체이다. 다음 상황을 잠시 살펴보자. 문자열이 벡터에 저장되어 있다고 가정한다. 그 벡터를 내림차순으로 정렬하고 싶다면 다음과 같이 stringVec
벡터를 간단하게 정렬할 수 있다.
sort(stringVec.begin(), stringVec.end(), greater<string>());마지막 인자는 생성자로 인식한다.
greater<>
클래스 템플릿을 구체화하여 문자열(string
)에 적용한다. 이 객체를 sort
총칭 알고리즘에서 함수객체로 호출한다. 이 총칭 알고리즘은 그 함수객체의 operator()
멤버를 호출해서 두 개의 string
객체를 비교한다. 함수객체의 operator()
는 이어서 string
데이터 유형의 operator>
를 호출한다. 마침내 sort
가 돌아오면 벡터의 첫 번째 원소에 모든 원소 중 가장 큰 값을 가지는 string
이 들어 있게 된다.
함수객체의 operator()
자체는 이 시점에서 보이지 않는다. `greater<string>()
' 인자에 있는 반괄호와 호출하는 operator()
의 반괄호를 서로 혼동하지 마라. operator()
가 실제로 sort
안에 사용될 때, `크기' 비교를 할 문자열 두 개를 인자로 받는다. greater<string>::operator()
연산자는 인라인으로 정의되어 있으므로 호출 자체는 실제로 위의 sort
호출에 존재하지 않는다. 대신에 sort
는 greater<string>::operator()
를 통하여 string::operator>
를 호출한다.
생성자가 총칭 알고리즘에 인자로 건네진다는 사실을 알았으므로 스스로 함수객체를 설계할 수 있다. 대소문자를 구별하지 않고 벡터를 정렬하고 싶다고 해 보자. 어떻게 처리할 것인가? 먼저, 기본(오름 차순)의 string::operator<
는 적절하지 않음을 알 수 있다. 대소문자를 구별해 비교하기 때문이다. 그래서 따로 CaseInsensitive
클래스를 공급하자. 두 개의 문자열을 대소문자를 구별하지 않고 비교한다. 다음 프로그램은 POSIX
strcasecmp
함수를 사용하여 임무를 수행한다. 대소문자를 구별하지 않고 명령줄 인자를 오름차순으로 정렬한다.
#include <iostream> #include <string> #include <cstring> #include <algorithm> using namespace std; class CaseInsensitive { public: bool operator()(string const &left, string const &right) const { return strcasecmp(left.c_str(), right.c_str()) < 0; } }; int main(int argc, char **argv) { sort(argv, argv + argc, CaseInsensitive()); for (int idx = 0; idx < argc; ++idx) cout << argv[idx] << " "; cout << '\n'; }
class CaseInsensitive
의 기본 생성자를 사용하여 sort
에 마지막 인자로 제공한다. 그래서 CaseInsensitive::operator()
만 정의하면 된다. string
인자로 호출될 것임을 알고 있으므로 두 개의 string
인자를 기대하도록 정의한다. 이 인자들은 strcasecmp
를 호출할 때 사용된다. 게다가 operator()
연산자는 인라인으로 정의되어 있으므로 sort
함수가 호출될 때의 부담을 덜어준다. sort
함수는 문자열(string
)을 다양하게 조합하여 함수객체를 호출한다. 컴파일러가 인라인 요청을 받아들이면 실제로 두 개의 추가 함수 호출을 건너뛰고 strcasecmp
를 호출한다.
비교 함수객체는 미리 정의되어 있는 경우가 많다. 미리 정의된 함수객체는 빈번히 사용되는 많은 연산에 사용할 수 있다. 다음 절에 그 사용법을 예제와 함께 소개한다. 말미에 함수 어댑터(function adaptor)를 소개한다.
미리 정의된 함수객체는 총칭 알고리즘에 아주 많이 사용된다. 산술 연산과 관계 연산 그리고 논리 연산을 위해 존재한다. 미리 정의된 함수객체를 발전시켜 24.3절에서 비트 연산을 수행시켜 보겠다.
plus<Type>
함수객체를 사용할 수 있다. Type
을 size_t
로 바꾸면 size_t
값의 덧셈 연산자가 사용된다. Type
을 string
으로 바꾸면 문자열에 대한 덧셈 연산자가 사용된다. 예를 들어:
#include <iostream> #include <string> #include <functional> using namespace std; int main(int argc, char **argv) { plus<size_t> uAdd; // size_ts를 더하는 함수객체 cout << "3 + 5 = " << uAdd(3, 5) << '\n'; plus<string> sAdd; // 문자열을 더하는 함수객체 cout << "argv[0] + argv[1] = " << sAdd(argv[0], argv[1]) << '\n'; } /* 출력: a.out going 3 + 5 = 8 argv[0] + argv[1] = a.outgoing */
이것이 왜 유용한가? 함수객체는 (미리 정의된 데이터 유형말고도) 온갖 데이터 유형에 사용할 수 있음을 눈여겨보라. 함수객체가 호출하는 연산자를 데이터 유형이 지원하기만 하면 된다.
왼쪽 피연산자는 언제나 같은 변수이고 오른쪽 인자는 순서대로 한 배열의 모든 원소를 사용해야 할 경우가 있다. 배열 안의 모든 원소들의 합을 계산하든가 또는 텍스트 배열 안의 모든 문자열을 결합하고 싶다고 하자. 이런 상황에 함수객체를 편리하게 사용할 수 있다.
앞서 언급했듯이 함수객체는 총칭 알고리즘의 맥락에 중요하게 사용된다. 그래서 예제를 하나 더 살펴보자.
accumulate
총칭 알고리즘은 반복자 범위로 지정된 원소들을 모두 방문한다. 그리고 공통 원소와 범위 안의 각 원소들에 대하여 이항 연산을 수행한다. 반복자 범위로 지정된 모든 원소들을 방문한 후에 축적된 결과를 돌려준다. 이 알고리즘의 사용법은 아주 쉽다. 다음 프로그램은 모든 명령 줄 인자를 합산하여 최종 문자열을 인쇄한다.
#include <iostream> #include <string> #include <functional> #include <numeric> using namespace std; int main(int argc, char **argv) { string result = accumulate(argv, argv + argc, string(), plus<string>()); cout << "All concatenated arguments: " << result << '\n'; }
앞쪽의 두 인자는 방문할 원소의 범위 (반복자)를 정의하고 세 번째 인자는 string
이다. 이 이름없는 문자열 객체는 최초 값을 제공한다. 다음과 같이 사용해도 되는데
string("All concatenated arguments: ")이 경우
cout
서술문은 그냥 cout << result << '\n'
이면 된다. 문자열-덧셈 연산이 사용되었다. plus<string>
으로부터 호출된다. 결합된 문자열이 최종 반환된다.
Time
클래스를 정의해 보자. operator+
를 중복정의한다. 미리 정의된 plus
객체를 적용할 수도 있다. 새로 정의된 데이터 유형에 맞추어 재단되었으므로 시간을 추가하려면:
#include <iostream> #include <string> #include <vector> #include <functional> #include <numeric> using namespace std; class Time { friend ostream &operator<<(ostream &str, Time const &time); size_t d_days; size_t d_hours; size_t d_minutes; size_t d_seconds; public: Time(size_t hours, size_t minutes, size_t seconds); Time &operator+=(Time const &rValue); }; Time &&operator+(Time const &lValue, Time const &rValue) { Time ret(lValue); return std::move(ret += rValue); } Time::Time(size_t hours, size_t minutes, size_t seconds) : d_days(0), d_hours(hours), d_minutes(minutes), d_seconds(seconds) {} Time &Time::operator+=(Time const &rValue) { d_seconds += rValue.d_seconds; d_minutes += rValue.d_minutes + d_seconds / 60; d_hours += rValue.d_hours + d_minutes / 60; d_days += rValue.d_days + d_hours / 24; d_seconds %= 60; d_minutes %= 60; d_hours %= 24; return *this; } ostream &operator<<(ostream &str, Time const &time) { return cout << time.d_days << " days, " << time.d_hours << " hours, " << time.d_minutes << " minutes and " << time.d_seconds << " seconds."; } int main(int argc, char **argv) { vector<Time> tvector; tvector.push_back(Time( 1, 10, 20)); tvector.push_back(Time(10, 30, 40)); tvector.push_back(Time(20, 50, 0)); tvector.push_back(Time(30, 20, 30)); cout << accumulate ( tvector.begin(), tvector.end(), Time(0, 0, 0), plus<Time>() ) << '\n'; } // 출력: 2 days, 14 hours, 51 minutes and 30 seconds.
위 프로그램의 설계는 보이는 그대로이다. Time
은 생성자를 정의하고 삽입 연산자를 정의하며 그리고 자신만의 operator+
연산자를 정의해 두 개의 시간 객체를 더한다. main
함수에서 네 개의 Time
객체가 vector<Time>
객체 안에 저장된다. 다음, 축적된 시간을 accumulate
로 계산한다. Time
객체를 돌려주면 이 객체를 cout
에 삽입한다.
첫 예제는 이름 붙은 함수객체의 사용법을 보여주었지만 마지막 두 예제는 (accumulate
) 함수에 건네어진 이름 없는 객체의 사용법을 보여주었다.
STL은 다음의 산술 함수객체를 지원한다. 산술 함수객체의 operator()
함수 호출 연산자는 건네받은 객체에 부합하는 산술 연산자를 호출하고 그 반환 값을 돌려준다. 실제로 호출되는 산술 연산자를 아래에 기술한다.
plus<>
: operator+
덧셈 이항 연산자를 호출한다.
minus<>
: operator-
뺄셈 이항 연산자를 호출한다.
multiplies<>
: operator*
곱셈 이항 연산자를 호출한다.
divides<>
: operator/
나눗셈 이항 연산자를 호출한다.
modulus<>
: operator%
나머지 이항 연산자를 호출한다.
negate<>
: operator-
부인 단항 연산자를 호출한다. 인자를 하나만 받는 단항 함수객체이다.
transform
총칭 알고리즘을 사용하여 배열 안에 있는 원소의 부호를 모두 뒤집는다. transform
알고리즘이 기대하는 두 개의 반복자는 변형할 객체의 범위를 정의한다. 목표 범위의 시작이 정의된 반복자와 (첫 인자와 같은 반복자일 수 있다) 그리고 지시된 데이터 유형에 대하여 단항 연산을 정의한 함수객체를 기대한다.
#include <iostream> #include <string> #include <functional> #include <algorithm> using namespace std; int main(int argc, char **argv) { int iArr[] = { 1, -2, 3, -4, 5, -6 }; transform(iArr, iArr + 6, iArr, negate<int>()); for (int idx = 0; idx < 6; ++idx) cout << iArr[idx] << ", "; cout << '\n'; } // 출력: -1, 2, -3, 4, -5, 6,
==, !=, >, >=, <
그리고 <=
표준 관계 연산자를 모두 지원한다.
STL은 다음의 관계형 함수객체 집합을 지원한다. 관계형 함수객체의 operator()
함수 호출 연산자는 건네받은 객체에 부합하는 관계 연산자를 호출한 다음에 그 반환 값을 돌려준다. 실제로 호출되는 관계 연산자를 아래에 기술한다.
equal_to<>
: operator==
를 호출한다.
not_equal_to<>
: operator!=
를 호출한다.
greater<>
: operator>
를 호출한다.
greater_equal<>
: operator>=
를 호출한다.
less<>
: operator<
를 호출한다.
less_equal<>
: operator<=
를 호출한다.
sort
와 조합하여 관계형 함수객체를 사용하는 예는 다음과 같다.
#include <iostream> #include <string> #include <functional> #include <algorithm> using namespace std; int main(int argc, char **argv) { sort(argv, argv + argc, greater_equal<string>()); for (int idx = 0; idx < argc; ++idx) cout << argv[idx] << " "; cout << '\n'; sort(argv, argv + argc, less<string>()); for (int idx = 0; idx < argc; ++idx) cout << argv[idx] << " "; cout << '\n'; }
예제는 문자열을 알파벳 순서로 저장하고 뒤집는 방법을 보여준다. greater_equal<string>
을 건네면 문자열은 내림차순으로 저장된다 (첫 단어는 '가장 큰' 단어가 된다). less<string>
를 건네면 문자열은 오름차순으로 저장된다 (첫 단어는 '가장 작은' 단어가 된다).
argv
에 char *
값이 들어 있고 관계 함수객체는 string
을 기대한다는 것을 주의하라. char const *
으로부터 string
으로 조용하게 승격된다.
and
와 or
그리고 not
논리 연산자를 지원한다.
STL은 다음의 논리 함수객체 집합을 지원한다. 이 함수객체의 operator()
함수 호출 연산자는 건네받은 객체에 부합하는 논리 연산자를 호출하고 그 반환 값을 돌려준다. 실제로 호출되는 논리 연산자는 아래에 기술한다.
operator!
를 사용하는 예를 다음 프로그램에 보여준다. transform
알고리즘을 사용하여 논리 값을 배열로 변환한다.
#include <iostream> #include <string> #include <functional> #include <algorithm> using namespace std; int main(int argc, char **argv) { bool bArr[] = {true, true, true, false, false, false}; size_t const bArrSize = sizeof(bArr) / sizeof(bool); for (size_t idx = 0; idx < bArrSize; ++idx) cout << bArr[idx] << " "; cout << '\n'; transform(bArr, bArr + bArrSize, bArr, logical_not<bool>()); for (size_t idx = 0; idx < bArrSize; ++idx) cout << bArr[idx] << " "; cout << '\n'; } /* 출력: 1 1 1 0 0 0 0 0 0 1 1 1 */
minus<int>
함수객체의 첫 매개변수가 100에 묶여 있다면 결과 값은 언제나 100에서 두 번째 인자의 값을 뺀 것이다.
원래는 bind1st
와 bind2nd
두 개의 바인더 어댑터가 정의되어 있었다. 각각 이항 함수의 첫 번째 인자와 두 번째 인자를 나타낸다. 그렇지만 다음 C++17 표준에서 bind1st
와 bind2nd
는 제거될 가능성이 높다. 좀 더 일반적인 bind
바인더로 교체할 수 있기 때문이다. bind
함수 자체가 비추천될 가능성이 높다. 총칭적인 람다 함수로 어렵지 않게 교체할 수 있기 때문이다 (18.7절).
bind1st
와 bind2nd
를 여전히 사용할 수 있기 때문에 bind2nd
를 중심으로 짧은 예제에 그 사용법을 보여준다. 좀 더 우아하게 bind
를 사용하는 방법은 다음 예제에 보여준다. 기존의 코드는 bind
나 람다 함수를 사용하도록 수정해야 한다.
bind
를 또는 std::placeholders
이름공간을 사용하기 전에 <functional>
헤더를 포함해야 한다.
다음은 bind2nd
의 사용법을 보여준다. vs
문자열 벡터에서 target
문자열과 같은 원소의 갯수를 센다 (필요한 데이터와 using namespace std
가 이미 지정되어 있다고 간주한다):
count_if(vs.begin(), vs.end(), bind2nd(equal_to<string>(), target));이 예제에서 문자열에 대하여
equal_to
함수객체가 구체화된다. target
을 두 번째 인자로 받고 vs
안의 문자열마다 하나씩 연속적으로 첫 인자에 건넨다. 이 특별한 예제에서 동등성이 결정되는 곳에 bind1st
를 사용할 수도 있었다.
bind
어댑터는 함수를 첫 인자로 기대한다. 그 다음에 함수가 필요한 인자를 얼마든지 건네도 된다. bind
를 사용할 때 갯수 없이 인자를 지정하고 있지만 C 언어에서 정의하는 방식의 가변 함수가 아니다. bind
는 가변 함수 템플릿이다 (22.5절).
기본값으로 bind
는 첫 인자로 지정된 함수를 돌려준다. 그리고 건네어진 나머지 인자들을 받는다. 그러면 bind
가 돌려준 함수를 호출할 수 있다. bind
가 호출된 방식에 따라, 반환된 함수를 호출하면 인자를 요구할 수도 안할 수도 있다.
다음은 한 예이다.
int sub(int lhs, int rhs); // lhs - rhs를 돌려준다. bind(sub, 3, 4); // 함수객체를 돌려준다. // operator()는 sub(3, 4)을 돌려준다.
bind
의 반환 값은 함수객체이므로 호출할 수 있다.
bind(sub, 3, 4)();그러나
bind
의 반환 값은 변수에 할당되는 경우가 더 많다. 그러면 반환된 객체를 다음과 같이 나타낸다.
auto functor = bind(sub, 3, 4); // 함수객체를 위한 변수 cout << functor() << '\n'; // 함수객체를 호출, -1을 돌려줌.
bind
를 사용할 때 인자를 지정하는 대신에 위치 보유자를 지정할 수 있다 (4.1.3.1목). 반환된 객체를 호출할 때 인자 값을 명시적으로 지정해야 한다. 다음은 몇 가지 예이다.
using namespace placeholders; auto ftor1 = bind(sub, _1, 4); // 첫 번째 인자를 지정해야 한다. ftor1(10); // 10 - 4 = 6을 반환 auto ftor2 = bind(sub, 5, _1); // 두 번째 인자를 지정해야 한다. ftor2(10); // 5 - 10 = -5를 반환 auto ftor3 = bind(sub, _1, _2); // 두 인자를 모두 지정해야 한다. ftor3(10, 2); // 10 - 2 = 8을 반환 auto ftor3 = bind(sub, _2, _1); // 두 인자를 모두 지정해야 한다. ftor3(10, 2); // 그러나 순서는 거꾸로이다. // 2 - 10 = -8을 반환대안으로 첫 인자는 멤버 함수의 주소가 될 수 있다. 그 경우에 첫 인자는 멤버 함수가 호출될 객체를 지정해야 한다. 반면에 나머지 인자들은 (있다면) 멤버 함수에 건네어질 인자들을 지정한다. 다음은 몇 가지 예이다.
struct Object // 이 객체는 뺄셈 연산의 { // lhss가 들어 있다. int d_lhs; Object(int lhs) : d_lhs(lhs) {} int sub(int rhs) // sub는 d_lhs를 변경한다. { return d_lhs -= rhs; } }; int main() { using namespace placeholders; Object obj(5); auto ftor = bind(&Object::sub, obj, 12); cout << ftor() << '\n'; // -7을 보여준다. cout << obj.d_x << '\n'; // obj는 변경되지 않는다. bind가 사본을 사용하기 때문이다. auto ftor = bind(&Object::sub, ref(obj), 12); cout << ftor() << '\n'; // -7을 보여준다. cout << obj.d_x << '\n'; // obj가 변경됨, cout은 -7을 보여줌 }두 번째
bind
호출에 ref
를 사용한 것에 주목하라: 여기에서 obj
는 참조로 건네진다. for2
함수객체에 사본이 아니라 obj
자체를 전달한다. 이것은 완벽한 전달(perfect forwarding)이라는 편의기능을 사용하여 실현된다. 이에 관해서는 22.5.2항에 더 자세하게 논의한다.
함수객체가 호출한 함수의 반환 유형이 문맥에 부합하지 않으면 (예를 들어 함수객체가 표현식 안에서 호출되었는데 반환 값이 size_t
라면) 적절한 유형으로 변환할 수 있다. 그 경우 bind
다음 옆꺽쇠 사이에 반환 유형을 요청할 수 있다. 예를 들어,
auto ftor = bind<size_t>(sub, _1, 4); // ftor의 반환 유형은 size_t이다. size_t target = 5; if (target < ftor(3)) // -1은 거대한 양수 값이 된다. cout << "smaller\n"; // 그래서 'smaller'를 보여준다.마지막으로
bind2nd
를 사용한 이전 예제는 bind
를 사용하여 다음과 같이 다시 작성할 수 있다.
using namespace placeholders; count_if(vs.begin(), vs.end(), bind(equal_to<string>(), _1, target));여기에서
bind
는 인자를 하나 기대하는 함수객체를 돌려준다 (_1
로 표현됨). 그리고 count_if
는 vs
에 있는 문자열들을 순서대로 bind
가 돌려주는 함수객체에 건넨다. 두 번째 인자는 (target
) 함수객체의 구현 안에 내장된다. 거기에서 equal_to<string>()
함수객체에 두 번째 인자로 건네진다.
bind1st
와 bind2nd
에 짝을 맞추어 두 개의 부인자 함수 어댑터가 미리 정의되어 있었다. not1
부인자는 단항 진위 함수와 함께 사용되고 not2
부인자는 이항 진위 함수와 함께 사용된다. 어떤 상황에서는 여전히 bind
함수 템플릿과 조합하여 사용하면 유용하다. 그러나 bind1st
와 bind2nd
는 C++17에서 제거될 것이므로 not1
와 not2
에 대해서도 대책을 마련해야 한다 (https://isocpp.org/files/papers/n4076.html 참고).
not1
과 not2
는 여전히 C++ 표준이므로 다음에 그 사용법을 간략하게 보여준다. 어떻게 미래의 not_fn
이 설계될 것인가 그리고 그 사용법을 제시하면서 그 대안을 22.5.5항에 구현해 본다.
다음은 not1
과 not2
의 사용법을 보여주는 예제들이다. 문자열 벡터 (vs
)에서 어떤 참조 문자열 (target
) 보다 먼저 알파벳 순서로 먼저 오는 원소의 갯수를 세려면 다음 대안 중 하나를 사용할 수 있다.
count_if(vs.begin(), vs.end(), bind2nd(less<string>(), target))또는
bind
사용하기:
count_if(vs.begin(), vs.end(), bind(less<string>(), _1, target));
not2
부인자를 사용하면 거꾸로 비교할 수 있다.
count_if(vs.begin(), vs.end(), bind2nd(not2(greater_equal<string>()), target));여기에서
not2
가 사용된다. greater_equal
의 진리 값을 부인하기 때문이다. Not2
는 두 개의 인자를 (즉, vs
의 원소 하나와 target
를) 받아, 그것을 greater_equal
에 건네고 greater_equal
함수를 호출하여 돌려받은 값을 부인하여 돌려준다.
이 예제에 bind
도 사용할 수 있다.
count_if(vs.begin(), vs.end(), bind(not2(greater_equal<string>()), _1, target));
not1
을 bind2nd
진위 함수와 결합해 사용: 여기에서 not1
의 함수 호출 연산자에 건넨 인자들은 (즉, vs
벡터의 원소들은) bind2nd
의 함수 호출 연산자에 건네진다. 이번에는 target
을 두번째 인자로 사용하여 greater_equal
함수객체를 호출한다. 그러면 bind2nd
의 함수 호출 연산자가 돌려준 값은 이어서 not1
함수 호출 연산자에 의하여 부인되어 반환된다.
count_if(vs.begin(), vs.end(), not1(bind2nd(greater_equal<string>(), target)))이 예제에
bind
를 사용하면 컴파일 에러가 일어난다. 이 에러는 not_fn
을 사용하면 피할 수 있다 (22.5.5항).
<iterator>
헤더를 포함해야 한다.
표준 반복자도 비추천 후보이지만 곧바로 표준 라이브러리로부터 제거된다는 뜻은 아니다. 자신만의 수레 바퀴를 발명하는 짓은 별로 효율적인 전략은 아니다. 대신에 공식적으로 교체될 때까지는 std::iterator
클래스를 사용하자.
반복자는 포인터처럼 행위하는 객체이다. 반복자는 일반적으로 다음과 같은 특징이 있다.
==
와 !=
연산자를 사용하여 두 개의 반복자가 동등한지 비교할 수 있다. 일반적으로 (>
, <
) 순서 연산자는 사용할 수 없다.
iter
가 주어지면 *iter
는 그 반복자가 가리키는 객체를 대표한다. 다른 방법으로 iter->
를 사용하면 반복자가 가리키는 객체의 멤버에 도달할 수 있다.
++iter
또는 iter++
는 반복자를 다음 원소로 전진시킨다. 결과적으로 반복자를 다음 원소로 전진시킨다는 개념이 적용된다. 어떤 컨테이너는 역방향 반복자 유형을 지원한다. 이런 유형에서 ++iter
연산은 실제로는 연속열에서 앞의 원소에 도달한다.
iter + 2
는 iter
가 가리키는 원소 다음에 두 번째 원소를 가리킨다. std::distance
를 다루는 18.2.1항도 참고하라.
#include <vector> #include <iostream> using namespace std; int main() { vector<int>::iterator vi; cout << &*vi; // 출력: 0 }
iterator
유형을 정의한다. 이 멤버들을 정방향 반복자에 대하여 begin
과 end
라고 부른다. 역방향 반복자에 대하여 rbegin
과 rend
이라고 부른다.
표준 관례는 반복자 범위에 왼쪽이 포함된다. [left, right)
표기법은 left
가 첫 원소를 가리키는 반복자이고, 반면에 right
는 마지막 원소 바로 다음을 가리키는 반복자라는 뜻이다. left == right
이면 반복자 범위는 비어 있다.
다음 예제는 문자열 벡터의 모든 원소들을 어떻게 cout
에 삽입하는지 보여준다. 반복자 [begin(), end())
범위와 [rbegin(), rend())
범위를 사용한다. 두 범위 모두에 대하여 for
회돌이는 동일하다. 그리고 어떻게 auto
키워드로 회돌이 제어 변수의 유형을 정의하는지 보여준다. vector<string>::iterator
와 같이 난해하게 변수를 정의하지 않은 점을 눈여겨보라 (3.3.5항):
#include <iostream> #include <vector> #include <string> using namespace std; int main(int argc, char **argv) { vector<string> args(argv, argv + argc); for (auto iter = args.begin(); iter != args.end(); ++iter) cout << *iter << " "; cout << '\n'; for (auto iter = args.rbegin(); iter != args.rend(); ++iter) cout << *iter << " "; cout << '\n'; }
또 STL은 상수 컨테이너 안의 원소들을 방문할 때 사용해야 하는 const_iterator
상수 반복자 유형을 정의한다. 이전 예제의 벡터 원소들은 변경할 수 있었지만 다음 예제에서는 변경이 불가능하다. 그리고 상수 반복자를 요구한다.
#include <iostream> #include <vector> #include <string> using namespace std; int main(int argc, char **argv) { vector<string> const args(argv, argv + argc); for ( vector<string>::const_iterator iter = args.begin(); iter != args.end(); ++iter ) cout << *iter << " "; cout << '\n'; for ( vector<string>::const_reverse_iterator iter = args.rbegin(); iter != args.rend(); ++iter ) cout << *iter << " "; cout << '\n'; }
이 예제들은 평범한 포인터를 반복자로 사용할 수 있음을 보여준다. vector<string> args(argv, argv + argc)
를 초기화하면 args
벡터에 포인터 기반의 반복자 한 쌍이 주어진다. argv
는 args
를 초기화할 첫 인자를 가리킨다. argv + argc
는 사용될 마지막 인자 바로 다음을 가리킨다. ++argv
는 다음 명령 줄 인자에 도달한다. 이것은 표준 포인터의 특징이다. 이 덕분에 반복자를 기대하는 상황에도 사용할 수 있다.
STL은 다섯 가지 유형의 반복자를 정의한다. 이 반복자 유형은 총칭 알고리즘에서 기대하며 특정 유형의 반복자를 손수 생성하려면 그 특징을 이해하는 것이 중요하다. 일반적으로 반복자는 다음과 같이 정의한다 (22.14절):
operator==
: 두 반복자가 같은지 테스트
operator!=
: 두 반복자가 같지 않은지 테스트
operator++
: 전위 연산자로서 반복자를 증가시킴
operator*
: 반복자가 참조하는 요소에 접근
InputIterator
: 입력 반복자
입력 반복자를 사용하여 컨테이너로부터 읽는다. 역참조 연산자는 표현식에서rvalue
로 작동함을 보장한다. 입력 반복자 대신에 정방향 반복자나 양방향 반복자 또는 무작위 접근 반복자를 사용하는 것도 가능하다 (아래 참고).InputIterator1
과InputIterator2
같은 표기법도 사용할 수 있다. 이 경우에 멤버들을 사용하여 어느 반복자가 `함께 속하는지' 표시할 수 있다. 예를 들어inner_product
총칭 알고리즘은 원형이 다음과 같다.Type inner_product(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, Type init);InputIterator1 first1
과InputIterator1 last1
는 한 범위에 한 쌍의 입력 반복자를 정의한다. 반면에InputIterator2 first2
는 또다른 범위의 시작을 정의한다. 다른 반복자 유형에도 유사한 표기법을 사용할 수 있다.
OutputIterator
: 출력 반복자
출력 반복자를 사용하여 컨테이너에 쓸 수 있다. 역참조 연산자는 표현식에lvalue
로 작동함을 보장한다. 그러나 꼭rvalue
로 작동해야할 필요는 없다. 출력 반복자 대신에 정방향 반복자나 양방향 반복자 또는 무작위 접근 반복자를 사용하는 것도 가능하다 (아래 참고).
ForwardIterator
: 정방향 반복자
정방향 반복자는 입력 반복자와 출력 반복자를 조합해 사용한다. 읽기/쓰기를 위해 한 방향으로 컨테이너를 순회할 수 있다. 정방향 반복자 대신에 양방향 반복자 또는 무작위 접근 반복자를 사용하는 것도 가능하다 (아래 참고).
BidirectionalIterator
: 양방향 반복자
양방향 반복자를 사용하여 양방향으로 컨테이너를 순회하며 읽고 쓸 수 있다. 양방향 반복자 대신에 무작위 접근 반복자를 사용하는 것도 가능하다 (아래 참고).
RandomAccessIterator
: 무작위 접근 반복자
무작위 접근 반복자는 컨테이너의 원소에 무작위로 접근한다. sort
같은 알고리즘은 무작위 접근 반복자를 요구한다. 그러므로 리스트나 맵의 원소를 정렬하는 데 사용할 수 없다. 양방향 반복자만 제공하기 때문이다.
std::list
또는 std::unordered_map
에서 포인터 연산을 사용하여 두 반복자 사이의 원소의 갯수를 계산하는 것은 불가능하다. 이런 컨테이너는 원소들이 메모리에 연속적으로 저장되지 않기 때문이다.
std::distance
함수는 이 작은 틈새를 메워 준다. std::distance
는 두 개의 입력반복자(InputIterator)를 기대하고 둘 사이에 있는 원소의 갯수를 돌려준다.
distance
함수를 사용하기 전에 먼저 <iterator>
헤더를 포함해야 한다.
첫 인자로 지정된 반복자가 두 번째 인자로 지정된 반복자를 초과하면 원소의 갯수는 양수가 아니다. 그렇지 않으면 음수가 아니다. 원소의 갯수를 결정할 수 없다면 (반복자가 같은 컨테이너에 있는 원소를 참조하지 않으면) distance
의 반환 값은 정의되어 있지 않다.
예제:
#include <iostream> #include <unordered_map> using namespace std; int main() { unordered_map<int, int> myMap = {{1, 2}, {3, 5}, {-8, 12}}; cout << distance(++myMap.begin(), myMap.end()) << '\n'; // 출력: 2 }
copy
총칭 알고리즘은 세 개의 매개변수가 있다. 앞에서 두 개는 방문할 원소의 범위가 정의되고 세 번째에 복사 연산의 결과가 저장될 첫 위치가 정의된다.
copy
알고리즘으로 복사될 원소의 갯수는 미리 알 수 있는데 그 갯수를 포인터 연산으로 제공할 수 있기 때문이다. 그렇지만 포인터 연산을 사용할 수 없는 경우가 있다. 예를 들면 결과 원소의 갯수가 최초의 범위에 있던 원소의 갯수와 다를 경우가 있다. unique_copy
총칭 알고리즘이 바로 그런 경우이다. 그런 경우는 목표 컨테이너로 복사되는 원소의 갯수를 미리 알 수 없다.
이런 경우에 inserter 어댑터 함수로 목표 컨테이너에 원소를 삽입할 수 있다. 삽입 어댑터는 세 가지 유형이 있다.
back_inserter
: 컨테이너의 push_back
멤버를 호출하여 새 원소를 컨테이너의 끝에 추가한다. 예를 들어 source
의 모든 원소들을 역순으로 destination
의 끝에 복사하기 위해 copy
총칭 알고리즘을 사용하면:
copy(source.rbegin(), source.rend(), back_inserter(destination));
front_inserter
컨테이너의 push_front
멤버를 호출하여 새 원소를 컨테이너의 처음에 추가한다. 예를 들어 source
의 모든 원소들을 목표 컨테이너의 앞에 추가하려면 (그리하여 원소의 순서도 역순으로 바뀜):
copy(source.begin(), source.end(), front_inserter(destination));
inserter
는 컨테이너의 insert
멤버를 호출하여 새 원소를 지정된 시작 위치부터 추가한다. 예를 들어 source
의 모든 원소를 destination
의 앞쪽부터 목표 컨테이너에 복사한다. 새로 삽입된 원소를 넘어가는 기존의 원소는 뒤로 이동시킨다.
copy(source.begin(), source.end(), inserter(destination, destination.begin()));
typedef
를 두 가지 요구한다.
typedef Data value_type
Data
는 push_back
이나 push_front
또는 insert
멤버를 제공하는 클래스에 저장된 데이터 유형이다 (예를 들어:
typedef std::string value_type
);
typedef value_type const &const_reference
back_inserter
에 집중하여 보자. 이 반복자는 push_back
멤버를 지원하는 컨테이너의 이름을 기대한다. 삽입자의 operator()
멤버는 컨테이너의 push_back
멤버를 호출한다. 다음과 같은 클래스를 인터페이스에 추가하면 push_back
멤버를 지원하는 클래스의 객체는 무엇이든 back_inserter
에 인자로 건넬 수 있다.
typedef DataType const &const_reference;(
DataType const &
는 push_back
멤버 함수에 건네는 매개변수의 유형이다). 예를 들어:
#include <iostream> #include <algorithm> #include <iterator> using namespace std; class Insertable { public: typedef int value_type; typedef int const &const_reference; void push_back(int const &) {} }; int main() { int arr[] = {1}; Insertable insertable; copy(arr, arr + 1, back_inserter(insertable)); }
istream_iterator<Type>
를 사용하면 istream
객체의 반복자 집합을 정의할 수 있다. istream_iterator
반복자의 일반적 형태는 다음과 같다.
istream_iterator<Type> identifier(istream &in)
Type
은 istream
으로부터 읽은 데이터 원소의 유형이다. 반복자 범위에서 `시작' 반복자로 사용된다. Type
은 operator>>
가 istream
과 함께 정의되어 있기만 하면 어떤 유형이든 상관이 없다.
끝-반복자로 기본 생성자가 사용되고 스트림의 끝에 상응한다. 예를 들어,
istream_iterator<string> endOfStream;begin-iterator를 정의할 때 지정했던 스트림 객체는 기본 생성자에 언급하지 않는다.
back_inserter
와 istream_iterator
어댑터를 사용하면 스트림으로부터 얻는 모든 문자열을 컨테이너 안에 손쉽게 저장할 수 있다. 예를 들어 (익명의 istream_iterator
어댑터를 사용하면):
#include <iostream> #include <iterator> #include <string> #include <vector> #include <algorithm> using namespace std; int main() { vector<string> vs {"한글", "만세"}; copy(istream_iterator<string>(cin), istream_iterator<string>(), back_inserter(vs)); for ( vector<string>::const_iterator begin = vs.begin(), end = vs.end(); begin != end; ++begin ) cout << *begin << ' '; cout << '\n'; }
streambuf
객체에 입력 반복자를 사용할 수도 있다.
입력 연산을 지원하는 streambuf
객체로부터 읽어 들이기 위해 istreambuf_iterator
를 사용할 수 있다. 지원하는 연산들은 istream_iterator
에서도 사용할 수 있다. istream_iterator
반복자 유형과 다르게 istreambuf_iterator
는 세 개의 생성자를 지원한다.
istreambuf_iterator<Type>
:
기본istreambuf_iterator
생성자를 사용하여 반복자 범위의 끝 반복자를 생성한다.streambuf
로부터Type
유형의 값을 추출할 때 스트림의-끝 조건을 나타낸다.
istreambuf_iterator<Type>(streambuf *)
:istreambuf_iterator
를 정의할 때streambuf
를 포인터로 사용할 수도 있다. 반복자 범위의 시작 반복자를 나타낸다.
istreambuf_iterator<Type>(istream)
:
istreambuf_iterator
를 정의할 때 istream을 사용할 수도 있다.istream
의streambuf
에 접근한다. 그리고 또한 반복자 범위의 시작 반복자를 나타낸다.
istreambuf_iterator
와 ostreambuf_iterator
를 모두 사용하는 예제를 보여준다.
ostream_iterator<Type>
어댑터를 사용하면 ostream
을 OutputIterator를 기대하는 알고리즘에 건넬 수 있다. 두 개의 생성자를 사용하여 ostream_iterator
를 정의할 수 있다.
ostream_iterator<Type> identifier(ostream &outStream); ostream_iterator<Type> identifier(ostream &outStream, char const *delim);
Type
은 ostream
안에 삽입될 데이터 원소의 유형이다. 유형에 상관없이 operator<<
는 ostream
객체와 조합하여 정의된다. 뒤의 생성자를 사용하면 delimiter
문자열을 경계로 하여 Type
데이터 원소들을 따로따로 분리할 수 있다. 앞의 생성자는 가름자를 사용하지 않는다.
예제는 어떻게 istream_iterator
와 ostream_iterator
를 사용하여 파일의 정보를 또다른 파일에 복사하는지 보여준다. 아마도 여기에서 in.unsetf(ios::skipws)
를 사용하고 싶을 수 있다. ios::skipws
깃발을 내리기 위해 사용된다. 결과적으로 공백 문자들은 연산자에 의하여 그대로 반환된다. 파일은 문자 단위로 복사된다. 다음은 그 프로그램이다.
#include <iostream> #include <algorithm> #include <iterator> using namespace std; int main() { cin.unsetf(ios::skipws); copy(istream_iterator<char>(cin), istream_iterator<char>(), ostream_iterator<char>(cout)); }
streambuf
객체에 출력 반복자를 사용할 수도 있다.
출력 연산을 지원하는 streambuf
객체에 쓰기 위해 ostreambuf_iterator
를 사용할 수 있다. ostream_iterator
에도 사용할 수 있다. ostreambuf_iterator
는 두 개의 생성자를 지원한다.
ostreambuf_iterator<Type>(streambuf *)
:ostreambuf_iterator
를 정의할 때streambuf
를 가리킬 수 있다. 출력 반복자로 사용할 수 있다.
ostreambuf_iterator<Type>(ostream)
:ostreambuf_iterator
를 정의할 때 ostream을 사용할 수도 있다.ostream
의streambuf
에 접근하지만 출력 반복자로 사용할 수도 있다.
istreambuf_iterator
와 ostreambuf_iterator
를 모두 사용하는 예를 보여준다. streambuf
스트림에 직접적으로 접근하기 때문에 스트림과 스트림 깃발(flag)은 무시된다. 결론적으로 이전 절처럼 ios::skipws
를 내릴 필요가 없다. 반면에 다음 프로그램은 아마도 이전 절에 보여준 프로그램보다 훨씬 더 효율적일 것이다.
#include <iostream> #include <algorithm> #include <iterator> using namespace std; int main() { istreambuf_iterator<char> in(cin.rdbuf()); istreambuf_iterator<char> eof; ostreambuf_iterator<char> out(cout.rdbuf()); copy(in, eof, out); }
unique_ptr
)를 사용하려면 먼저 <memory>
헤더를 포함해야 한다.
동적으로 할당된 메모리에 포인터로 접근할 때 메모리 누수를 방지하려면 위치를 엄격하게 관리해야 한다. 동적으로 할당된 메모리를 참조하는 포인터 변수가 영역을 벗어나면 그 메모리는 접근할 수 없으며 프로그램은 메모리 누수를 경험한다.
그런 메모리 누수를 방지하려면 위치를 엄격하게 관리해야 하므로 포인터 변수가 영역을 벗어나기 전에 프로그래머는 동적으로 할당된 메모리가 공용 풀에 제대로 반납되는지 확인해야 한다.
동적으로 할당한 한 개의 값이나 객체를 포인터 변수가 가리키면 위치를 관리하기가 대단히 단순해진다. 포인터 변수를 독점 포인터로 정의할 수 있기 때문이다.
독점 포인터는 포인터를 대신하는 객체이다. 객체이기 때문에 영역을 벗어날 때 소멸자가 호출된다. 소멸자는 자신이 가리키는 동적으로 할당된 메모리를 자동으로 삭제한다. 독점 포인터(unique_ptr
)와 그의 사촌인 공유 포인터(shared_ptr
)는 스마트 포인터라고 불리운다 (18.4절).
독점 포인터는 몇 가지 특징이 있다.
std::unique_ptr<int> up1(new int); std::unique_ptr<int> up2(up1); // 컴파일 에러두 번째 정의는 컴파일에 실패한다. 독점 포인터의 복사 생성자가 비밀 멤버이기 때문이다. 할당 연산자에도 마찬가지이다. 그러나 독점 포인터는 rvalue 참조로부터 할당하고 초기화하는 편의기능을 제공한다.
class unique_ptr // 부분적으로 보여지는 인터페이스 { public: unique_ptr(unique_ptr &&other); // 여기에서 rvalue를 묶는다. private: unique_ptr(const unique_ptr &other); };다음 예제는 이동 의미구조를 사용한다. 그래서 올바르게 컴파일된다.
unique_ptr<int> cp(unique_ptr<int>(new int));
Derived
클래스가 Base
로부터 파생될 때, 새로 할당된 Derived
클래스 객체는 unique_ptr<Base>
에 할당할 수 있다. Base
에 대하여 가상 소멸자를 정의할 필요가 없다. unique_ptr
의 정의에 deleter
함수의 주소를 제공하기만 하면 독점 포인터 객체는 Base *
포인터를 돌려준다. 이 포인터를 그냥 정적으로 Derived
유형으로 변환하면 되며, Derived
클래스의 소멸자도 자동으로 호출된다. 다음 예제에 이를 보여준다.
class Base { ... }; class Derived: public Base { ... public: // Derived에 void process() 멤버가 있다고 가정한다. static void deleter(Base *bp); }; void Derived::deleter(Base *bp) { delete static_cast<Derived *>(bp); } int main() { unique_ptr<Base, void (*)(Base *)> bp(new Derived, &Derived::deleter); static_cast<Derived *>(bp.get())->process(); // OK! } // 여기에서 ~Derived가 호출된다. 다형성을 요구하지 않는다.
unique_ptr
클래스에서 제공하는 여러 멤버 함수로 포인터 자체에 접근하거나 독점 포인터가 또다른 메모리 블록을 가리키도록 만들 수 있다. 이런 멤버 함수를 그리고 독점 포인터의 생성자를 다음 몇 항에 소개한다.
공유 포인터(shared_ptr
)와 더불어 독점 포인터(unique_ptr
)는 지금은 비추천된 자동 포인터(auto_ptr
)를 대신하여 안전하게 사용할 수 있다 (18.4절). 독점 포인터는 품질도 자동 포인터를 능가한다. 맞춤 소멸자를 추가할 수 있기 때문에 컨테이너 그리고 총칭 알고리즘과 함께 사용할 수 있다. 게다가 배열도 독점 포인터로 처리할 수 있다.
<type>
지정자를 포함한다.
unique_ptr<type> identifier;
이 형태는 18.3.2항에 논의한다.
unique_ptr
인자는 더 이상 동적으로 할당된 메모리를 가리키지 않는다. 그리고 포인터 데이터 멤버는 0-포인터로 바뀐다.
unique_ptr<type> identifier(another unique_ptr for type);
이런 형태는 18.3.3항에 논의한다.
deleter
)를 제공할 수도 있다. unique_ptr
의 포인터를 인자로 받는 자유 함수를 또는 함수객체를 소멸자로 건넬 수 있다. 동적으로 할당된 메모리를 공용 풀에 반납하도록 되어 있다. 포인터가 0이면 아무 일도 하지 않는다.
unique_ptr<type> identifier (new-expression [, deleter]);
이 형태는 18.3.4항에 논의한다.
unique_ptr<type> identifier;독점 포인터가 제어하는 포인터는
0
(영)으로 초기화된다. unique_ptr
객체 자체는 포인터가 아니지만 그 값은 0
과 비교할 수 있다. 예제:
unique_ptr<int> ip; if (!ip) cout << "0-pointer with a unique_ptr object\n";대안으로,
get
멤버를 사용할 수 있다 (18.3.5항).
unique_ptr<type> identifier(other unique_ptr object);다음 예제에 이동 생성자가 사용된다.
void mover(unique_ptr<string> &¶m) { unique_ptr<string> tmp(move(param)); }비슷하게 할당 연산자를 사용할 수 있다.
unique_ptr
객체는 유형이 같은 임시 unique_ptr
객체에 할당할 수 있다. 역시 이동-의미구조가 사용된다. 예를 들어:
#include <iostream> #include <memory> #include <string> using namespace std; int main() { unique_ptr<string> hello1(new string("Hello world")); unique_ptr<string> hello2(move(hello1)); unique_ptr<string> hello3; hello3 = move(hello2); cout << // *hello1 << /\n' << // 세그먼트 폴트 에러가 일어날 것임 // *hello2 << '\n' << // 마찬가지 *hello3 << '\n'; } // 출력: Hello world
예제가 보여주는 바에 의하면
hello1
은 동적으로 할당된 string
을 가리키는 포인터로 초기화된다 (다음 절 참고).
unique_ptr hello2
는 이동 생성자를 사용하여 hello1
이 제어하는 포인터를 획득한다. 이것은 hello1
을 0-포인터로 바꾸는 효과가 있다.
hello3
이 기본 unique_ptr<string>
로 정의된다. 그러나 다음 이동-할당을 사용하여 그의 값을 hello2
으로부터 획득한다. 결과적으로 역시 0-포인터로 바뀐다.
hello1
이나 hello2
가 cout
으로 삽입되면 세그먼트 폴트 에러가 일어날 것이다. 그 이유는 이제 명백하다. 0-포인터를 역참조했기 때문이다. 결국, hello3
만 실제로 원래 할당된 string
을 가리킨다.
unique_ptr<type [, deleter_type]> identifier(new-expression [, deleter = deleter_type()]);두 번째
deleter(_type)
인자는 선택적이다. 할당된 메모리를 반납하는 자유 함수나 함수객체를 참조한다. 이중 포인터가 할당되어 있고 내포된 포인터마다 방문하여 할당된 메모리를 파괴하는 소멸자이다 (아래 예시 참고).
다음 예제는 문자열 객체를 가리키는 독점 포인터를 초기화한다.
unique_ptr<string> strPtr(new string("Hello world"));생성자에 건네어진 인자는
operator new
가 돌려준 포인터이다. 유형에 그 포인터가 언급되지 않는 것을 눈여겨보라. 독점 포인터의 생성에 사용된 유형은 new
표현식에 사용된 유형과 똑같다.
다음 예제는 명시적으로 소멸자를 정의하여 어떻게 동적으로 할당된 문자열을 가리키는 포인터 배열을 삭제하는지 보여준다.
#include <iostream> #include <string> #include <memory> using namespace std; struct Deleter { size_t d_size; Deleter(size_t size = 0) : d_size(size) {} void operator()(string **ptr) const { for (size_t idx = 0; idx < d_size; ++idx) delete ptr[idx]; delete[] ptr; } }; int main() { unique_ptr<string *, Deleter> sp2(new string *[10], Deleter(10)); Deleter &obj = sp2.get_deleter(); }
독점 포인터를 사용하면 new
표현식에 의해 할당된 실체의 멤버 함수에 도달할 수 있다. 독점 포인터는 마치 동적으로 할당된 객체를 가리키는 평범한 포인터인 것처럼 이런 멤버에 도달할 수 있다. 다음 프로그램은 `hello
' 단어 뒤에 `C++
' 문자열을 삽입한다.
#include <iostream> #include <memory> #include <cstring> using namespace std; int main() { unique_ptr<string> sp(new string("Hello world")); cout << *sp << '\n'; sp->insert(strlen("Hello "), "C++ "); cout << *sp << '\n'; } /* 출력: Hello world Hello C++ world */
unique_ptr<Type> &operator=(unique_ptr<Type> &&tmp)
:이 연산자는 이동 의미구조를 사용하여 rvalue 독점 포인터가 가리키는 메모리를 lvalue 독점 포인터로 이전한다. 그래서 rvalue 객체는 자신이 가리키던 메모리를 잃어버리고 0-포인터로 바뀐다. 기존의 독점 포인터를 또다른 독점 포인터에 할당할 수 있다.std::move
를 사용하여 먼저 rvalue 참조로 변환하면 된다. 예제:unique_ptr<int> ip1(new int); unique_ptr<int> ip2; ip2 = std::move(ip1);
operator bool() const
:독점 포인터가 메모리를 가리키지 않으면 (즉,get
멤버가 0을 돌려주면) 이 연산자는false
를 돌려준다 (아래 참고). 그렇지 않으면true
가 반환된다.
Type &operator*()
:이 연산자는 독점 포인터를 통하여 접근할 수 있는 정보를 참조로 돌려준다. 마치 평범한 포인터 역참조(dereference) 연산자처럼 행위한다.
Type *operator->()
:이 연산자는 독점 포인터를 통하여 접근할 수 있는 정보를 포인터로 돌려준다. 이 연산자로 독점 포인터를 통하여 객체에 접근해 멤버를 선택할 수 있다. 예를 들어:unique_ptr<string> sp(new string("hello")); cout << sp->c_str();
Type *get()
: 독점 포인터가 통제하는 정보를 포인터로 돌려준다. 마치 operator->
처럼 행위한다. 반환된 포인터를 조사할 수 있다. 만약 0이면 독점 포인터는 아무 메모리도 가리키지 않는다.
Deleter &unique_ptr<Type>::get_deleter()
:독점 포인터가 사용하는 소멸자를 참조로 돌려준다.
Type *release()
: 독점 포인터를 통하여 접근할 수 있는 정보를 포인터로 돌려준다. 동시에 객체 자체는 0-포인터가 된다. 즉, 포인터 데이터 멤버가 0-포인터로 바뀐다. 이 멤버를 사용하면 독점 포인터를 통하여 접근할 수 있는 정보를 평범한 Type
포인터에 전달할 수 있다. 이 멤버를 호출하고 난 후, 동적으로 할당된 메모리를 파괴하는 것은 프로그래머의 책임이다.
void reset(Type *)
:독점 포인터가 통제하는 동적으로 할당된 메모리를 공용 풀에 반납한다. 그 때부터 이 멤버 함수에 건넨 인자가 가리키는 메모리는 독점 포인터가 통제한다. 이 멤버 함수는 인자 없이 호출할 수도 있다. 그러면 독점 포인터는 0-포인터로 바뀐다. 이 멤버 함수를 사용하면 동적으로 할당된 새 메모리 블록을 독점 포인터에 할당할 수 있다.
void swap(unique_ptr<Type> &)
:유형이 같은 두 개의 독점 포인터를 서로 바꾼다.
동적으로 할당된 배열에 다음 구문을 사용할 수 있다.
[]
) 사용하여 동적으로 할당된 배열을 스마트 포인터가 통제하도록 지정한다. 예를 들어:
unique_ptr<int[]> intArr(new int[3]);
intArr[2] = intArr[0];
delete
가 아니라 delete[]
를 호출한다.
전통적으로 C++에는 자동 포인터(auto_ptr
)가 있다. 자동 포인터는 이동 의미구조를 지원하지 않는다. 그럼에도 자동 포인터를 또다른 객체에 할당하면 오른쪽 객체는 자신의 정보를 잃는다.
독점 포인터는 자동 포인터의 단점이 없다. 결론적으로 자동 포인터는 이제 사용을 권장하지 않는다. 자동 포인터는 다음의 단점이 있다.
이 단점 때문에 그리고 대체할 수 있기 때문에 자동 포인터는 더 이상 C++ 주해서에 다루지 않는다. 기존의 소프트웨어는 독점 포인터나 공유 포인터 같은 스마트 포인터를 사용하도록 변경해야 한다. 그리고 새 소프트웨어는 되도록이면 이 새로운 스마트 포인터 유형의 관점에서 직접적으로 구현해야 한다.
shared_ptr
)를 사용할 수 있다. 참조 횟수를 세는 스마트 포인터이다.
공유 포인터를 사용하기 전에 <memory>
헤더를 포함해야 한다.
공유 포인터는 참조 카운터가 0으로 줄어들면 자동으로 그의 내용을 파괴한다. 독점 포인터처럼 shared_ptr<Base>
를 사용하여 새로 할당된 Derived
클래스 객체를 저장할 때 반환된 Base *
를 Derived *
로 static_cast
를 사용하여 형변환할 수 있다. 다형성은 요구되지 않는다. 공유 포인터를 재초기화하거나 공유 포인터가 영역을 벗어날 때 슬라이싱(객체를 복사하면 파생 객체는 소실되고 바탕 객체만 복사되는 현상 (복사 손실)-재정의 불가능)이 일어나지 않고, Derived
의 소멸자가 호출된다 (18.3절).
공유 포인터는 복사 생성자와 이동 생성자는 물론이고 표준 할당 연산자와 중복정의 이동 할당 연산자도 지원한다.
독점 포인터처럼 공유 포인터도 동적으로 할당된 배열을 참조할 수 있다.
<type>
지정자가 있다.
shared_ptr<type> identifier;이런 형태는 18.4.2항에 다루었다.
shared_ptr<string> org(new string("hi there")); shared_ptr<string> copy(org); // 참조 횟수는 이제 2이다.
grabber
를 생성한다. grabber
의 생성자는 익명의 임시 객체를 받으므로 컴파일러는 공유 포인터의 이동 생성자를 사용한다.
shared_ptr<string> grabber(shared_ptr<string>(new string("hi there")));
deleter
)를 제공할 수 있다. 공유 포인터를 인자로 받는 자유 함수나 함수객체를 소멸자에 건넬 수 있다. 동적으로 할당된 메모리를 공용 풀에 돌려주도록 되어 있다. 포인터가 0이면 아무 일도 하지 않는다.
shared_ptr<type> identifier (new-expression [, deleter]);이 형태는 18.4.3항에 다루었다.
shared_ptr<type> identifier;공유 포인터가 통제하는 포인터는
0
(영)으로 초기화된다. 공유 포인터 자체는 포인터가 아니지만 그의 값은 0
과 비교할 수 있다. 예를 들어:
shared_ptr<int> ip; if (!ip) cout << "0-pointer with a shared_ptr object\n";다른 방법으로서
get
멤버를 사용할 수도 있다 (18.4.4항).
shared_ptr<type> identifier(new-expression [, deleter]);두 번째 인자(
deleter
)는 선택적이다. 할당된 메모리를 파괴하는 자유 함수나 함수객체를 참조한다. 이중 포인터가 할당되고 내포된 포인터마다 방문하면서 할당된 메모리를 파괴하는 소멸자이다 (아래 예시 참고). 독점 포인터가 마주하는 상황에 사용된다 (18.3.4항).
다음은 문자열 객체를 가리키는 공유 포인터를 초기화하는 예이다.
shared_ptr<string> strPtr(new string("Hello world"));생성자에 건네진 인자는
operator new
가 반환하는 포인터이다. type
에 포인터가 전혀 보이지 않는 것을 눈여겨보라. 공유 포인터 생성에 사용된 유형은 new
표현식에 사용된 유형과 똑같다.
다음 예제는 두 개의 공유 포인터가 실제로 정보를 공유하는 것을 보여준다. 한쪽에서 정보를 변경하면 변경된 정보가 다른 쪽에 보인다.
#include <iostream> #include <memory> #include <cstring> using namespace std; int main() { shared_ptr<string> sp(new string("Hello world")); shared_ptr<string> sp2(sp); sp->insert(strlen("Hello "), "C++ "); cout << *sp << '\n' << *sp2 << '\n'; } /* 출력: Hello C++ world Hello C++ world */
shared_ptr &operator=(shared_ptr<Type> const &other)
:복사 할당한다. 왼쪽 피연산자의 참조 횟수가 감소한다. 참조 횟수가 0으로 줄어들면 왼쪽 피연산자가 통제하던 동적으로 할당된 메모리는 삭제된다. 다음 그 정보를 오른쪽 피연산자와 공유한다. 정보의 참조 횟수를 증가시킨다.
shared_ptr &operator=(shared_ptr<Type> &&tmp)
:이동 할당한다. 왼쪽 피연산자의 참조 횟수가 감소한다. 참조 횟수가 0으로 줄어들면 왼쪽 피연산자가 통제하던 동적으로 할당된 메모리는 삭제된다. 다음 오른쪽 피연산자가 통제하는 정보를 획득한다. 그 때문에 0-포인터로 바뀐다.
operator bool() const
:공유 포인터가 실제로 메모리를 가리키면true
가 반환된다. 그렇지 않으면false
가 반환된다.
Type &operator*()
:공유 포인터에 저장된 정보를 참조로 돌려준다. 보통의 포인터처럼 작동한다.
Type *operator->()
:공유 포인터가 제어하는 정보를 포인터로 돌려준다. 예제:shared_ptr<string> sp(new string("hello")); cout << sp->c_str() << '\n';
Type *get()
:공유 포인터가 통제하는 정보를 포인터로 돌려준다. 마치 operator->
처럼 행위한다. 반환된 포인터를 조사할 수 있다. 0이면 공유 포인터가 메모리를 가리키지 않는 것이다.
Deleter &get_deleter()
:공유 포인터의 소멸자(함수 또는 함수객체)를 참조로 돌려준다.
void reset(Type *)
:공유 포인터가 통제하는 정보의 참조 횟수를 줄인다. 0으로 줄면 메모리가 삭제된다. 그 이후로 그 객체의 정보는 함수에 건넨 인자를 참조하고, 자신의 공유 횟수를 1로 설정한다. 인자 없이 호출하면 객체를 0-포인터로 변환한다. 동적으로 할당된 새 메모리 블록을 공유 포인터에 할당할 수 있다.
void reset(Type *, DeleterType &&)
:이전 멤버의 변형으로서 특정한Deleter
유형을 받는다.Type
이 바탕 클래스이고 파생 클래스 객체가 사용되면 이 파생 클래스 객체는 소멸 시점에 특정한 행위를 요구할 수도 있다. 이전 멤버가 사용되면, 결과적으로 새로 할당된 객체의 소멸자가 호출된다. 소멸 함수를 명시적으로 사용하지 않는다. 이 멤버는 공유 횟수가 0으로 줄어들면 제공된 소멸자를 확실하게 호출한다.
void shared_ptr<Type>::swap(shared_ptr<Type> &&)
:유형이 동일한 공유 포인터 두 개를 서로 바꾼다.
bool unique() const
:공유 포인터가 통제하는 메모리를 현재 객체가 유일하게 참조하고 있으면true
가 반환된다. 그렇지 않으면 (객체가 0-포인터인 상황을 포함하여)false
가 반환된다.
size_t use_count() const
:메모리를 공유하는 객체의 갯수를 돌려준다.
struct Base {}; struct Derived: public Base {};
새로 할당된 Derived
클래스 실체를 저장하기 위하여 shared_ptr<Base>
를 정의하면 Base *
유형이 반환된다. 이 반환 유형은 단독 포인터처럼 static_cast
를 사용하여 Derived *
으로 형변환할 수도 있다. 다형성은 요구하지 않는다. 공유 포인터를 재초기화하거나 공유 포인터가 영역을 벗어나더라도 슬라이싱(복사손실)은 일어나지 않는다. 그리고 Derived
의 소멸자가 호출된다 (18.3절).
물론 shared_ptr<Derived>
은 쉽게 정의할 수 있다. Derived
객체는 Base
객체이기도 하므로 굳이 형변환하지 않아도 Derived
를 가리키는 포인터는 Base
를 가리키는 포인터로 간주해도 된다. 그러나 static_cast
를 사용하면 Derived *
를 Base *
로 번역하도록 강제할 수 있다.
Derived d; static_cast<Base *>(&d);
그렇지만 Derived
객체를 가리키는 공유 포인터의 get
멤버를 사용하여 공유 포인터를 Base
로 변환할 때 평범한 static_cast
는 사용할 수 없다. 다음 코드는 결국 동적으로 할당된 Base
객체를 두 번 파괴하려고 시도하게 될 것이다.
shared_ptr<Derived> sd(new Derived); shared_ptr<Base> sb(static_cast<Base *>(sd.get()));
sd
와 sb
는 같은 객체를 가리키기 때문에 sb
와 sd
가 영역을 벗어나면 ~Base
소멸자가 두 번 호출된다. 그 때문에 프로그램은 이르게 종료하게 된다. 두 번 해제하는 에러가 일어나기 때문이다.
이 에러는 공유 포인터와 함께 사용되도록 특별히 설계된 형변환을 사용하면 방지할 수 있다. 이 형변환은 특정화된 생성자를 사용한다. 생성된 공유 포인터는 메모리를 가리키지만 소유권을 (즉, 참조 횟수를) 기존의 공유 포인터와 공유한다. 이 특별한 형변환은 다음과 같다.
std::static_pointer_cast<Base>(std::shared_ptr<Derived> ptr)
:Base
클래스를 가리키는 공유 포인터를 돌려준다. 반환된 공유 포인터는shared_ptr<Derived> ptr
가 참조하는Derived
클래스에서 바탕 클래스 부분을 참조한다. 예제:shared_ptr<Derived> dp(new Derived()); shared_ptr<Base> bp = static_pointer_cast<Base>(dp);
std::const_pointer_cast<Class>(std::shared_ptr<Class const> ptr)
:Class
클래스를 가리키는 공유 포인터를 돌려준다. 반환된 공유 포인터는 가변Class
객체를 참조한다. 반면에ptr
인자는Class const
객체를 참조한다. 예제:shared_ptr<Derived const> cp(new Derived()); shared_ptr<Derived> ncp = const_pointer_cast<Derived>(cp);
std::dynamic_pointer_cast<Derived>(std::shared_ptr<Base> ptr)
:Derived
클래스 객체를 가리키는 공유 포인터를 돌려준다.Base
클래스는 적어도 하나의 가상 멤버 함수를 가져야 하며,Base
를 상속받은Derived
클래스는Base
의 가상 멤버를 재정의할 수도 있다.Base *
으로부터Derived *
으로 동적 변환에 성공하면 반환된 공유 포인터는Derived
클래스 객체를 참조한다. 동적 형변환에 실패하면 공유 포인터의get
멤버는 0을 돌려준다. 예를 들어 (Derived
와Derived2
를Base
로부터 상속받았다고 가정함):shared_ptr<Base> bp(new Derived()); cout << dynamic_pointer_cast<Derived>(bp).get() << ' ' << dynamic_pointer_cast<Derived2>(bp).get() << '\n';첫 번째get
은 0-아닌 포인터를 돌려준다. 두 번째get
은 0을 돌려준다.
그러나 독점 포인터처럼 공유 포인터로 배열을 참조하면 역참조 연산자는 의미가 없어진다. 대신에 공유 포인터는 인덱스 연산자를 이용할 수 있다.
그런 편의기능을 제공하는 shared_array
클래스를 만드는 것은 어렵지 않다. shared_ptr
클래스로부터 상속받아 shared_array
클래스 템플릿을 만들고 거기에 소멸자만 주면 배열과 그의 원소들을 파괴해 준다. 게다가 인덱스 연산자를 정의하면 선택적으로 delete
를 사용하여 역참조 연산자를 선언할 수 있다.
다음은 어떻게 shared_array
를 정의하고 사용하는지 보여준다.
struct X { ~X() { cout << "destr\n"; // 객체의 소멸을 보여준다. } }; template <typename Type> class shared_array: public shared_ptr<Type> { struct Deleter // 소멸자는 포인터를 받는다. { // 그리고 delete[]를 호출한다. void operator()(Type* ptr) { delete[] ptr; } }; public: shared_array(Type *p) // 다른 생성자는 : // 여기에 없다. shared_ptr<Type>(p, Deleter()) {} Type &operator[](size_t idx) // 인덱스 연산자 { return shared_ptr<Type>::get()[idx]; } Type const &operator[](size_t idx) const { return shared_ptr<Type>::get()[idx]; } Type &operator*() = delete; // 포인터가 없는 멤버는 제거한다. Type const &operator*() const = delete; Type *operator->() = delete; Type const *operator->() const = delete; }; int main() { shared_array<X> sp(new X[3]); sp[0] = sp[1]; }
std::shared_ptr<string> sptr(new std::string("hello world"))이 서술문은 메모리를 두 번 할당한다. 하나는
std::string
를 할당하는 데 그리고 다른 하나는 내부적으로 공유 포인터의 생성자 자체에서 사용한다.
make_shared
템플릿을 사용하면 두 번의 할당을 하나의 할당으로 조합할 수도 있다 (shared_ptr
의 생성자를 명시적으로 호출하는 것보다 약간 더 효율적이기도 하다). std::make_shared
함수 템플릿은 원형이 다음과 같다.
template<typename Type, typename ...Args> std::shared_ptr<Type> std::make_shared(Args ...args);
make_shared
를 사용하기 전에 <memory>
헤더를 포함해야 한다.
이 함수 템플릿은 Type
유형의 객체를 할당하고 args
를 생성자에 건네며 (완벽한 전달(perfect forwarding)을 사용, 22.5.2항) 그리고 새로 할당된 Type
객체의 주소로 초기화된 공유 포인터를 돌려준다.
다음은 std::make_shared
를 사용하여 위의 sptr
객체를 초기화하는 방법이다. auto
키워드를 눈여겨보라. 덕분에 sptr
의 유형을 명시적으로 지정하지 않아도 된다.
auto sptr(std::make_shared<std::string>("hello world"));이렇게 초기화하고 나면
std::shared_ptr<std::string> sptr
이 정의되고 초기화된다. 다음과 같이 사용할 수 있다.
std::cout << *sptr << '\n';
C++14 표준은 또 std::make_unique
을 제공한다. 이를 make_shared
처럼 사용할 수 있지만 공유 포인터가 아니라 독점 포인터를 만들어 준다.
class Filter { istream *d_in; ostream *d_out; public: Filter(char const *in, char const *out); };
Filter
객체는 *d_in
으로부터 읽은 정보를 여과해 그 정보를 *d_out
에 쓴다고 가정하자. 스트림을 가리키는 포인터를 사용하면 종류에 관계없이 istream
이나 ifstream
또는 fstream
이나 istringstream
스트림을 필터 객체가 가리키도록 만들 수 있다. 위에 보여준 생성자는 다음과 같이 구현할 수 있다.
Filter::Filter(char const *in, char const *out) : d_in(new ifstream(in)), d_out(new ofstream(out)) { if (!*d_in || !*d_out) throw string("Input and/or output stream not available"); }당연히 생성에 실패할 가능성이 있다.
new
가 예외를 던지거나 스트림 생성자가 예외를 던지거나 또는 스트림이 생성자 몸체에서 예외를 던질 경우 열리지 않을 수도 있다. 함수를 사용하여 try 블록을 시도하면 도움이 된다. d_in
의 초기화가 예외를 던지더라도 아무 것도 걱정할 것이 없음을 눈여겨보라. Filter
객체는 생성되지 않았다. 그의 소멸자는 호출되지 않는다. 그리고 예외가 잡힌 곳까지 처리가 계속 진행된다.
그러나 d_out
의 초기화나 생성자의 if
서술문에서 예외를 던지면 Filter
의 소멸자도 호출되지 않는다. 객체도 없고 그러므로 소멸자도 호출되지 않는다. 이 때문에 메모리 누수가 생길 수 있다. d_in
와 d_out
에 대하여 delete
가 호출되지 않기 때문이다. 이를 방지하기 위하여 d_in
과 d_out
을 먼저 0으로 초기화해야 한다. 그 다음에 초기화를 수행할 수 있다.
Filter::Filter(char const *in, char const *out) try : d_in(0), d_out(0) { d_in = new ifstream(in); d_out = new ofstream(out); if (!*d_in || !*d_out) throw string("Input and/or output stream not available"); } catch (...) { delete d_out; delete d_in; }그렇지만 이렇게 하면 즉시 복잡해진다. 만약
Filter
가 한 클래스의 또다른 데이터 멤버를 품고 있고 그의 생성자가 두 개의 스트림을 필요로 하면 그 데이터는 생성할 수 없다. 아니면 그 자체를 포인터로 변환해야 한다.
Filter::Filter(char const *in, char const *out) try : d_in(0), d_out(0) d_filterImp(*d_in, *d_out) // 작동하지 않는다. { ... } // 대신에 다음을 사용: Filter::Filter(char const *in, char const *out) try : d_in(0), d_out(0), d_filterImp(0) { d_in = new ifstream(in); d_out = new ofstream(out); d_filterImp = new FilterImp(*d_in, *d_out); ... } catch (...) { delete d_filterImp; delete d_out; delete d_in; }후자의 대안이 작동하겠지만 혼란스러워진다. 이와 같은 혼란은 스마트 포인터를 사용하여 방지한다. 스트림 포인터를 스마트 포인터 객체로 정의해 생성하면 나머지 생성자의 코드가 예외를 던지더라도 적절하게 파괴된다.
FilterImp
와 두 개의 단독 포인터를 사용한다면 Filter
의 설정과 그의 생성자는 다음과 같다.
class Filter { std::unique_ptr<std::ifstream> d_in; std::unique_ptr<std::ofstream> d_out; FilterImp d_filterImp; ... }; Filter::Filter(char const *in, char const *out) try : d_in(new ifstream(in)), d_out(new ofstream(out)), d_filterImp(*d_in, *d_out) { if (!*d_in || !*d_out) throw string("Input and/or output stream not available"); }다시 원래의 구현으로 되돌아 왔다. 그러나 이 번에는 날 포인터나 메모리 누수에 관하여 걱정할 필요가 없다. 초기화 멤버 중 하나가 예외를 던지더라도 (지금은 객체가 되어 있는) 이전에 생성된 데이터 멤버의 소멸자들이 언제나 호출된다.
제일 규칙으로서 클래스가 포인터 데이터 멤버를 정의할 필요가 있을 때 생성자에서 에러를 던질 가능성이 조금이라도 있다면 스마트 포인터로 정의해야 한다.
sort
(19.1.58항) 그리고 find_if
(19.1.16항) 총칭 알고리즘이 있다. 제일 규칙으로서 호출된 함수가 자신의 상태를 기억하려면 함수객체가 적절하다. 그렇지 않으면 평범한 함수를 사용하면 된다.
함수객체나 함수가 언제나 준비 상태에 있는 것은 아니다. 사용할 곳 근처에 반드시 정의되어 있어야 한다. 이것은 익명의 이름공간에 함수나 클래스를 정의하여 실현한다. (예를 들어 A라는 클래스 또는 함수라면) A를 필요로 하는 코드에 A를 건넨다. 그 코드 자체가 클래스 B의 멤버 함수이면 A의 구현은 클래스 B의 멤버에 접근함으로써 혜택을 누릴 수 있다.
이 체계는 (클래스를 정의하기 때문에) 일반적으로 코드가 폭증하거나 (A의 코드에 자동으로 접근할 수 없는 요소들을 사용가능하도록 만들어야 하기 때문에) 코드가 복잡해진다. 또한 현재 수준의 규격에 맞지 않는 코드를 생산할 수도 있다. 내포 클래스도 이 문제를 해결하지 못한다. 더구나 내포 클래스는 템플릿에 사용할 수 없다.
람다 표현식이 이 문제를 해결해 준다. 람다 표현식은 익명의 (이름없는) 함수객체를 정의한다. 다음 몇 항목에 설명하듯이 함수객체를 인자로 기대하는 함수에 이 익명 함수를 바로 건네도 된다.
람다 표현식은 블록이나 클래스 또는 이름공간 안에 (즉, 원하는 곳이면 어디든지) 사용된다. 묵시적인 클로저 유형은 람다 표현식을 담고 있는 이름공간 또는 클래스 같은 초소형 블록 안에 정의가 된다. 클로저 객체는 정의가 시작되는 시점에서 보이고 정의가 끝나면 보이지 않는다.
클로저 유형은 (const
) 공개 인라인 함수 호출 연산자를 정의한다. 다음은 람다 표현식의 예이다.
[] // `람다-도입부' (int x, int y) // `람다-선언부' { // 보통의 복합 서술문 return x * y; }이 람다 표현식으로 생성된 클로저 객체의 함수 호출 연산자는 두개의
int
인자를 기대하고 그 곱을 돌려준다. 클로저 유형의 인라인 const
멤버이다. const
속성을 걷어 내려면 람다 표현식에 다음과 같이 mutable
을 지정해야 한다.
[](int x, int y) mutable ...람다-선언부는 매개변수가 없다면 생략해도 된다. 람다 선언부의 매개변수는 기본 인자를 제공하지 않아도 된다.
위의 람다 표현식으로 정의된 클로저 객체를 accumulate
(19.1.1항) 총칭 알고리즘과 조합하여 사용하면 벡터에 저장된 일련의 int
값들의 곱을 얻을 수 있다.
cout << accumulate(vi.begin(), vi.end(), 1, [](int x, int y) { return x * y; });위의 람다 함수는 묵시적으로
decltype(x * y)
유형을 돌려준다. 다음과 같은 경우 유형을 묵시적으로 반환한다.
return
서술문이 없다 (즉, void 람다 표현식이다);
return
서술문이 하나 뿐이다.
return
서술문이 여러 개이다. 유형이 동일한 값들을 반환한다 (예를 들어 모조리 int
값이다).
여러 return
서술문이 있어서 다른 유형의 값을 반환하면 늦게-지정되는 반환 유형을 사용하여 (3.3.5항) 람다 표현식의 반환 유형을 명시적으로 지정해야 한다.
[](int x, int y) -> int { return y < 0 ? x / static_cast<double>(y) : z + x; }
람다 표현식의 위치에서 보이는 변수에 접근할 수 있다. 어떻게 이 변수에 접근할 것인지는 람다-도입부의 내용에 달려 있다 (각괄호 사이의 람다-나포 구역). 람다-나포부로 지역 문맥을 람다 표현식에 건넬 수 있다.
보이는 전역 변수와 정적 변수는 물론 람다 표현식의 복합 서술문 자체에 정의된 지역 변수도 직접적으로 접근해 변경할 수 있다. 예를 들어:
int global; void fun() { []() // [] 안에 얼마든지 지정할 수 있다. { int localVariable = 0; localVariable = ++global; }; }
람다 표현식이 (비-정적) 클래스 멤버 함수 안에 정의되고 람다-나포부에서 처음에 &
또는 =
문자를 사용하면 this
포인터를 사용할 수 있다. 그래서 람다 표현식은 클래스의 모든 멤버에 접근할 수 있다. 이 경우 람다 표현식은 클래스의 데이터 멤버를 변경할 수도 있다.
람다 표현식이 함수 안에 정의되어 있으면 그 시점에 보이는 함수의 모든 지역 변수에 접근할 수 있다.
람다-나포부에 있는 처음의 &
문자는 이 지역 변수에 참조로 접근한다. 그러면 이 변수들은 람다 표현식 안으로부터 변경할 수 있다.
람다-나포부에 있는 처음의 =
문자는 참조되는 지역 변수의 지역 사본을 생성한다. 이 경우 람다 표현식이 mutable
키워드로 정의되어 있으면 지역 사본의 값들은 람다 표현식만 변경할 수 있다는 것에 주목하라. 예를 들어,
struct Class { void fun() { int var = 0; [=]() mutable { ++var; // fun 함수의 var가 아니라 } // 지역 사본을 변경한다. } }
C++17 표준에서 람다-표현식을 (비-정적) 멤버 함수에 정의할 때 람다-나포부 안에 *this
를 포함할 수도 있다. 굳이 지정하지 않더라도 람다 표현식은 묵시적으로 this
포인터를 나포하고, this
포인터에 상대적으로 클래스 멤버에 언제든지 접근할 수 있다. 그러나 멤버를 비동기적으로 호출하면 문제가 야기될 수 있다. 비동기적으로 람다 함수를 호출하면 그 시점에 생애를 마친 그리하여 이미 존재하지 않는 객체의 멤버를 참조할 가능성이 있기 때문이다. 이 잠재적 문제는 [=, *this]
와 같이 =
으로 시작하고 `, *this
'를 람다 나포 안에 사용하면 해결된다 (그리고 변수는 여전히 예와 같이 나포할 수 있다). `, *this
'를 지정하면 this
가 참조하는 객체가 명시적으로 나포된다: 그 객체의 영역이 끝나더라도 즉시 소멸되지 않지 않고, 그 표현식이 존재하는 동안 람다 표현식이 나포한다. `, *this
'를 지정하려면 그 객체는 직접적으로 사용할 수 있어야 한다. 다음 예제를 연구해 보자:
struct s2 { double ohseven = .007; auto f() { return [this] { return [*this] { return ohseven; // OK } }(); } auto g() { return [] { return [*this] { // 에러: 바깥의 람다 표현식에 // *this가 나포되지 않는다. }; }(); } };
세밀하게 조율할 수도 있다. 처음에 =
가 있고 쉼표로 갈라 &var
를 지정하면 언급된 지역 변수들을 사본이 아니라 참조로 처리해야 한다고 알려준다. 처음에 &
가 있고 쉼표로 갈라 var
를 지정하면 언급된 지역 변수의 지역 사본을 사용해야 한다고 알려준다. 람다 표현식에 mutable
키워드를 주지 않는 한, 이 사본들의 값은 변경할 수 없다.
람다-나포부에 this
를 사용하여 세밀하게 조율할 수도 있다. 람다 표현식 둘레의 클래스 멤버에 접근할 수 있도록 해 준다. 예제:
class Data { std::vector<std::string> d_names; public: void show() const { int count = 0; std::for_each(d_names.begin(), d_names.end(), [this, &count](std::string const &name) { std::cout << ++count << ' ' << capitalized(name) << '\n'; } ); } private: std::string capitalized(std::string name); };
람다 표현식이 익명의 함수객체일지라도 변수에 할당할 수 있다. 변수는 auto
키워드를 사용해 정의하기도 한다. 예를 들어,
auto sqr = [](int x) { return x * x; };이런 람다 표현식의 생애는 람다 표현식을 값으로 받는 변수의 생애와 함께 한다.
먼저 (이름붙은) 명명 람다 표현식을 연구하자. 이름붙은 람다 표현식은 지역 함수의 틈새에 멋지게 맞아 들어간다. 함수가 자신의 임무 자체보다 개념적으로 낮은 단계에서 계산을 수행할 필요가 있을 때, 이 계산을 별도의 지원 함수에 싸 넣고 필요한 곳에서 그 함수를 호출하는 것이 매력적이다. 지원 함수들은 익명의 이름공간에 정의할 수는 있지만 그러면 요구하는 함수가 클래스 멤버이고 지원 함수도 그 클래스의 멤버에 접근해야 할 경우 즉시 이상해진다.
그렇다면 이름붙은 람다 함수를 사용할 수 있다. 요구하는 함수 안에 정의할 수 있고, 둘레의 클래스에 완전하게 접근할 수 있다. 람다 표현식에 할당된 이름은 둘레의 함수가 호출할 수 있는 함수의 이름이 된다. 다음 예는 숫자 IP 주소를 점으로 구분한 십진 문자열로 변환한다. Dotted
객체로부터 직접적으로 접근할 수도 있다 (간결하게 구현을 모두 클래스 안에 배치함).
class Dotted { std::string d_dotted; public: std::string const &dotted() const { return d_dotted; } std::string const &dotted(size_t ip) { auto octet = [](size_t idx, size_t numeric) { return to_string(numeric >> idx * 8 & 0xff); }; d_dotted = octet(3, ip) + '.' + octet(2, ip) + '.' + octet(1, ip) + '.' + octet(0, ip); return d_dotted; } };
다음으로 for_each
같은 총칭 알고리즘의 사용법을 연구해 보자 (19.1.17항).
void showSum(vector<int> const &vi) { int total = 0; for_each( vi.begin(), vi.end(), [&](int x) { total += x; } ); std::cout << total << '\n'; }람다 표현식에
int total
변수가 주소로 건네진다. 거기에 함수가 직접적으로 접근한다. 매개변수 리스트에 int x
만 단순하게 정의되어 있다. 이것은 vi
에 저장된 각각의 값으로 연속적으로 초기화된다. 이 총칭 알고리즘이 완료되면 showSum
의 total
변수는 벡터의 값을 모두 합한 값을 받는다. 람다 표현식보다 오래 생존하고 그 값이 화면에 표시된다.
그러나 총칭 알고리즘이 대단히 유용함에도 언제나 모든 과업에 딱 맞는 것은 아니다. 게다가 for_each
와 같은 알고리즘은 약간 다루기 어려워 보인다. 이제 범위 기반의 for-회돌이를 제공한다. 그래서 위의 구현 대신에 다음을 시도해 보자:
void showSum(vector<int> const &vi) { int total = 0; for (auto el: vi) [&](int x) { total += x; }; std::cout << total << '\n'; }그러나
showSum
함수를 호출하면 cout
서술문이 일관되게 0이라고 보고한다. 무슨 일이 일어난 것인가?
총칭 알고리즘에 람다 표현식을 주면 구현은 함수를 참조로 구체화한다. 그렇게 참조된 함수는 총칭 알고리즘의 내부로부터 호출된다. 그러나 위의 예제에서 범위 기반의 for-회돌이 안에 있는 서술문은 단순히 람다 표현식의 정의만 나타낼 뿐이다. 어떤 것도 실제도 호출되지 않았고 그래서 total
은 여전히 0과 같다.
그래서 위의 예제를 작동시키려면 람다 표현식을 정의해야 할 뿐만 아니라 호출도 해야 한다. 람다 함수에 이름을 준 다음에 그 이름으로 호출하면 된다.
void showSum(vector<int> const &vi) { int total = 0; for (auto el: vi) { auto lambda = [&](int x) { total += x; }; lambda(el); } std::cout << total << '\n'; }
실제로는 람다 함수에 이름을 줄 필요가 없다. auto lambda
정의면 람다 함수를 나타내고 그것을 직접적으로 호출할 수 있다. 구문이 좀 이상하게 보이지만 아무 잘못도 없다. 지난 예제에 필요했던 복합 서술문을 완전히 생략할 수 있다. 다음을 보자:
void showSum(vector<int> const &vi) { int total = 0; for (auto el: vi) [&](int x) { total += x; }(el); // 인자 리스트를 // 람다 함수의 정의에 // 곧바로 추가한다. std::cout << total << '\n'; }
람다 표현식은 condition_variable
의 wait
호출로부터의 가짜 반환을 방지할 수도 있다 (20.5.3항).
condition_variable
클래스로 그렇게 할 수 있다. 잠금과 진위함수를 기대하는 wait
멤버를 제공하면 된다. 진위 함수는 데이터의 상태를 점검하여 데이터 상태가 처리를 허용하면 true
를 돌려준다. 다음은 20.5.3항에 보여준 down
멤버를 다르게 구현한 것이다. 데이터를 실제로 사용했는지 점검한다.
void down() { unique_lock<mutex> lock(sem_mutex); condition.wait(lock, [&]() { return semaphore != 0 } ); --semaphore; }람다 표현식은
semaphore
가 증가할 때만 wait
가 반환된다고 보장한다.
람다 표현식은 주로 프로그램에서 매우 지역화된 부분에 사용되는 함수객체를 얻는다. 기존의 함수 안에서 사용되기 때문에 람다 함수를 사용하면 여러 수준이 집적되어 있다는 사실을 깨닫아야 한다. 함수는 단지 몇 개의 문장만을 사용하여 자신만의 집적 수준에서 기술이 가능한 과업을 구현한다. 예를 들어 ``std::sort
정렬 함수는 호출된 문맥에 적절하게 요소들을 비교함으로써 데이터 구조를 정렬한다''. 기존의 비교 방법을 사용함으로써 집적 수준이 유지되고 그 자체로 명백하게 서술된다. 예를 들어,
sort(data.begin(), data.end(), greater<DataType>());기존의 비교 방법을 사용할 수 없다면 맞춤 함수객체를 생성해야 한다. 이것은 람다 표현식을 사용하여 실현할 수 있다. 예를 들어,
sort(data.begin(), data.end(), [&](DataType const &lhs, DataType const &rhs) { return lhs.greater(rhs); } );뒤의 예제를 보면 두 개의 다른 수준이 집적되어 있음을 깨닫아야 한다. 상위 수준에서 의도는
data
의 원소들을 정렬하는 것이다. 그러나 내포 수준에서 (람다 표현식 안에서) 완전히 다른 일이 일어난다. 람다 표현식 안은 두 객체가 어느 쪽이 더 큰지 결정한다. 이런 식으로 여러 수준이 섞여 보이면 읽기에 어려우며 따라서 피하는 게 좋다.
반면에 람다 표현식은 코드를 간단하게 만들어 주기도 한다. 맞춤 함수객체를 정의하는 부담을 피할 수 있기 때문이다. 그러므로 람다 표현식을 아껴서 사용하기를 조언하는 바이다. 람다 표현식을 사용한다면 가능하면 크기를 작게 유지하라. 제일 규칙으로서 람다 표현식은 인라인 함수처럼 취급해야 한다. 단순히 하나의 표현식으로 구성하면 좋고 최대 두 개의 표현식을 넘어서면 좋지 않다.
auto
키워드로 매개변수를 정의한다. 인자의 실제 유형을 살펴서 적절한 람다 표현식을 생성한다. 총칭적이므로 서로 다른 유형의 인자를 가지고 한 함수 안에서 사용할 수 있다. 다음은 한 예이다 (필요한 모든 헤더와 이름공간이 선언되어 있다고 간주한다):
1: int main() 2: { 3: auto lambda = [](auto lhs, auto rhs) 4: { 5: return lhs + rhs; 6: }; 7: 8: vector<int> values {1, 2, 3, 4, 5}; 9: vector<string> text {"a", "b", "c", "d", "e"}; 10: 11: cout << 12: accumulate(values.begin(), values.end(), 0, lambda) << '\n' << 13: accumulate(text.begin(), text.end(), string(), lambda) << '\n'; 14: }
총칭 람다 함수는 줄 3부터 6까지 정의되어 있고 lambda
식별자에 할당된다. 다음, lambda
가 줄 12와 13에서 accumulate
에 건네진다. 줄 12에서 int
값을 더하도록 초기화되고 줄 13에서 std::string
값을 더하도록 초기화된다. 같은 lambda
가 두 개의 완전히 다른 함수객체(functor)로 초기화된다. main
안에서만 지역적으로 사용할 수 있다.
템플릿을 다루기 전에 맛보기로서 언급하면 (특히 제 21장) 총칭 람다 표현식은 클래스 템플릿과 동등하다. 이를 보여주기 위해 위의 일반화된 람다 함수의 예는 다음과 같은 클래스 템플릿을 사용하여 구현할 수도 있다.
struct Lambda { template <typename LHS, typename RHS> auto operator()(LHS const &lhs, RHS const &rhs) const { return lhs + rhs; } }; auto lambda = Lambda{};이렇게 결과가 동일하게 나오는 이유 하나는 람다 표현식의 매개변수 리스트에
auto
키워드를 사용하면 템플릿 인자 추론 규칙을 따르기 때문이다 (21.4항). 이것은 auto
키워드가 작동하는 방식과 약간 다르다.
C++14 표준에서 람다 표현식이 바깥 영역의 변수를 잡는 방법이 확장되었다. C++11은 값이나 참조로 나포한다. 이 결과 이동 생성만 지원하는 유형의 바깥 영역의 변수는 람다 함수에 값으로 건넬 수 없다. 이 제한은 C++14 표준에서 풀렸으므로 변수들을 어떤 표현식으로든 초기화할 수 있다. 이 덕분에 람다 도입부에서 변수를 이동-초기화할 수 있을 뿐만 아니라 람다 표현식의 바깥 영역에 상응하는 익명의 변수도 여기에서 초기화할 수 있다. 이 경우 다음과 같이 초기화 표현식을 사용할 수 있다.
auto fun = [value = 1] { return value; };이 람다 함수는 (물론) 1을 돌려준다. 선언된 나포식은 마치
auto
키워드가 사용된 것처럼 초기화 표현식으로부터 유형을 추론한다.
이동 초기화하려면 std::move
를 사용해야 한다. 예를 들어,
std::unique_ptr<int> ptr(new int(10)); auto fun = [value = std::move(ptr)] { return *value; };
총칭 람다 표현식에서 auto
키워드는 람다 함수가 구체화될 때 어느 유형을 사용할지 컴파일러가 결정한다는 것을 나타낸다. 그러므로 총칭 람다 표현식은 생긴 모습은 전혀 다르지만 클래스 템플릿이다 (22장). 다음 람다 표현식은 총칭 클래스 템플릿을 정의한다. 다음과 같이 사용할 수 있다.
char const *target = "hello"; auto lambda = [target](auto const &str) { return str == target; }; vector<string> vs{stringVectorFactory()}; find_if(vs.begin(), vs.end(), lambda);잘 작동하지만 이런식으로
lambda
를 정의하면 복잡한 에러 메시지를 맞을 준비를 해야 한다. 역참조된 반복자의 유형과 (조용하게 추론된) 람다의 str
유형이 일치하지 않을 경우가 있기 때문이다.
regcomp
함수나 regexec
함수를 제공해 왔다). 그러나 C++ 전용의 정규 표현식은 전통적인 C 보다 인터페이스가 더 풍부할 뿐만 아니라 템플릿을 사용하는 코드에도 사용할 수 있다.
C++ 전용의 정규 표현식을 사용하려면 먼저 <regex>
헤더를 포함해야 한다.
정규 표현식은 여기저기에 광범위하게 문서화가 되어 있다 (예를 들어 regex(7), Friedl, J.E.F Mastering Regular Expressions, O'Reilly) (정규 표현식 완전 해부와 실습(개정판) 역 서환수 한빛미디어 출간). 독자는 이런 교재들을 참조하여 정규 표현식이라는 주제를 다시 한 번 상기하시기를 바란다. 본질적으로 정규 표현식은 (`숫자', `식별자', 등등과 같은) 텍스트 단위를 인지하는 작은 메타 언어이다. 토큰과 연관된 입력 문자열을 정의할 때 어휘분석기에 널리 사용된다 (24.8.1항). 그러나 다른 상황에도 크게 쓰인다. sed(1) 그리고 grep(1) 같은 언어는 정규 표현식을 사용하여 파일에서 특정한 텍스트 조각들을 찾는다. perl(1) 같은 언어는`사탕 구문'을 추가하여 정규 표현식을 간단하게 생성한다.
그렇지만 지극히 유용함에도 불구하고 정규 표현식은 또한 몹시 읽기 어려운 것으로도 잘 알려져 있다. 어떤 사람들은 심지어 정규 표현식 언어를 쓰기-전용 언어라고 부르기도 한다. 정규 표현식을 만드는 동안은 왜 특별한 방법으로 작성되었는지 그 이유가 명확하게 드러난다. 그러나 그 반대로 정규 표현식이 무엇을 나타내는지 이해하는 것은 적절한 문맥을 모르면 지극히 어려울 수 있다. 그 때문에 처음부터 꼭 지켜야 할 제일 규칙으로서 정규 표현식의 각 부분마다 무엇에 부합해야 하는지 적절하게 주석을 반드시 붙이도록 하자.
다음 절은 먼저 정규 표현식 언어를 간략하게 개관한다. 다음에 정규 표현식 사용을 위해 C++가 제공하는 편의기능들을 연구한다. 이 편의기능들은 주로 클래스들로 구성된다. 정규 표현식을 지정하는 것을 돕고 정규 표현식을 텍스트에 부합해 보며 그리고 텍스트의 어느 부분이 (있다면) 분석 중인 텍스트의 어느 부분에 부합하는지 알아본다.
regex
클래스에서 기본으로 사용하는 정규 표현식의 특징이 기술되어 있다.
정규 표현식에 대한 C++의 기본 정의는 다음 아톰(원자 의미 단위)으로 구분한다.
x
: 문자 `x';
.
: 새줄 문자를 제외한 모든 문자;
[xyz]
: 문자 부류; 이 경우 `x', `y', 또는 `z'가 부합한다. 문자 부류에 관한 문단은 아래도 참고하라.
[abj-oZ]
: 한 범위의 문자를 담은 문자 부류; `a', a `b', `j'부터 `o'까지의 문자나 또는 `Z'에 부합한다. 문자 부류에 관한 문단은 아래도 참고하라.
[^A-Z]
: 부인 문자 부류: ^
다음의 문자 부류를 제외하고 어떤 문자에도 부합한다. 이 경우는 대문자를 제외하고 모든 문자에 부합한다. 문자 부류에 관한 문단을 아래에서 참고하라.
[:predef:]
: 미리 정의된(predefined) 문자 집합. 개관은 아래 참고. 문자 부류 안의 원소로 번역된다. 그러므로 언제나 이중 각 괄호 두 쌍 안에 문자 부류를 정의한다 (예, [[:alnum:]]
);
\X
: X가 `a', `b', `f', `n', `r', `t', 또는 `v'이면, `\x'를 ANSI-C로 번역한다. 그렇지 않으면 문자 그대로 `X'이다 (*
와 같은 연산자를 피신시킨다).
(r)
: r
은 정규 표현식이다. 우선 순위를 바꾼다 (아래 참고). 또한 r
을 표식 붙은 부-표현식으로 정의한다. 이 부 표현식에 부합하는 문자들을 std::smatch
객체로 열람할 수 있다 (18.8.3항).
(?:r)
: r
은 정규 표현식이다. 우선 순위를 바꾼다 (아래 참고). 그러나 표식 붙은 부-표현식으로 간주되지 않는다.
이런 기본적인 아톰 외에도 다음의 특별한 아톰들을 사용할 수 있다 (문자 부류에도 사용할 수 있다).
\s
: 공백 문자 하나;
\S
: 공백 문자가 아닌 모든 문자;
\d
: 십진 숫자;
\D
: 십진 숫자가 아닌 모든 문자;
\w
: 알파벳 숫자 또는 밑줄 문자 (_
);
\W
: 알파벳 숫자 또는 밑줄 문자 (_
)가 아닌 모든 문자.
아톰은 결합해 사용할 수 있다. r
과 s
가 아톰이면 정규 표현식 rs
는 목표 텍스트가 r
에 부합하고 그리고 s
에 순서대로 (목표 텍스트 안에 중간 문자 없이) 부합할 경우 목표 텍스트에 부합한다. 예를 들어 정규 표현식 [ab][cd]
는 목표 텍스트 ac
에 부합하지만 a:c
에는 부합하지 않는다.
아톰은 연산자로 결합할 수 있다. 연산자는 앞의 아톰에 묶인다. 연산자가 여러 아톰에 작동해야 한다면 그 아톰들은 괄호로 둘러싸야 한다 (앞의 아톰 설명에서 마지막 항목을 참고). 연산자 문자를 아톰으로 사용하려면 피신시키면 된다. 예를 들어 *
는 연산자를 나타내지만 \*
는 아톰 별표 문자를 나타낸다. 문자 부류는 피신 연속열을 인지하지 못한다는 것을 유념하라: [\*]
는 두 개의 문자로 구성된 문자 부류를 나타낸다. 역사선과 별표 두 개로 구성된 문자 부류이다.
다음 연산자를 지원한다 (r
과 s
는 정규 표현식의 아톰을 나타낸다):
r*
: 0개 이상의 r
;
r+
: 1개 이상의 r
;
r?
: 0 또는 1개의 r
(즉, 선택적인 r);
r{m, n}
: 최소 m개 최대 n 개의 `r'에 부합한다 (1 <= m <= n
);
r{m,}
: 최소 m 개의 `r'에 부합한다 (1 <= m
);
r{m}
: 정확하게 m 개의 `r'에 부합한다 (1 <= m
);
r|s
: `r' 또는 `s'에 부합한다. 이 연산자는 곱셈 연산자보다 우선 순위가 낮다.
^r
: ^
는 의사 연산자이다. 이 표현식은 `r'이 목표 텍스트의 시작에 나타나면 부합한다. ^
-문자가 정규 표현식의 맨 처음에 있지 않으면 문자 그대로 ^
-문자로 번역된다.
r$
: $
는 의사 연산자이다. 이 표현식은 `r'이 목표 텍스트의 맨 끝에 나타나면 부합한다. $
-문자가 정규 표현식의 맨 끝에 있지 않으면 문자 그대로 $
-문자로 번역된다.
정규 표현식에 표식 붙은 부-표현식과 빈도 지정자가 들어 있고 그 부-표현식이 여러 번 부합하면 목표의 최종 부-문자열이 부합한 것으로 보고된다. 예를 들어 regex_search
를 사용할 때 (18.8.4.3목) 표식 붙은 부-표현식이 ((a|b)+\s?)
이고 목표 텍스트가 a a b
이면 a a b
가 완전히 부합한 텍스트이다. 반면에 b
는 첫 번째와 두 번째 표식붙은 부-표현식에 부합한 부-문자열로 보고된다.
\s, \S, \d, \D, \w,
그리고 \W
의 특별한 아톰은 제외한다. 문자 범위 연산자 -
를 제외하고 문자 부류를 끝내는 연산자 ]
를 제외하며 그리고 문자 부류의 시작에 있는 ^
를 제외한다. 특수한 아톰과 결합되는 것을 제외하고 피신 문자는 글자 그대로 역사선 문자로 번역된다 (역사선과 d
를 담은 문자 부류를 정의하려면 그냥 [d\]
를 사용하라).
문자 부류에 닫는 각괄호를 추가하려면 최초의 여는-각괄호 바로 다음에 []
를 사용하라. 또는 닫는 각괄호가 담겨 있지 않은 부인된 문자 부류라면 [^]
으로 시작하라. 마이너스 문자는 문자 범위를 정의한다 (예를 들어 [a-d]
는 [abcd]
를 정의함) (실제 범위는 사용중인 로케일에 따라 달라질 수도 있다). 글자 그대로의 마이너스 문자를 문자 부류에 추가하려면 문자 부류의 맨 앞에 ([-
, 또는 [^-
) 또는 맨 뒤에 배치하라 (-]
).
문자 부류가 시작하면 모든 문자들은 마지막 닫는 각괄호 (]
)에 도달할 때까지 연이어서 문자 집합에 추가된다.
문자와 문자 범위 말고도 문자 부류는 미리 정의된 문자 집합을 담을 수 있다. 다음이 그것이다.
[:alnum:] [:alpha:] [:blank:] [:cntrl:] [:digit:] [:graph:] [:lower:] [:print:] [:punct:] [:space:] [:upper:] [:xdigit:]미리 정의된 이 집합은 상응하는 표준 C
isXXX
함수에 동등한 문자 집합을 가리킨다. 예를 들어 [:alnum:]
은 isalnum(3)이 참을 돌려주는 모든 문자를 정의한다.
(w)regex
클래스를 사용하기 전에 <regex>
헤더를 포함해야 한다.
std::regex
와 std::wregex
유형은 정규 표현식 패턴을 정의한다. 각각 basic_regex<char>
유형과 basic_regex<wchar_t>
유형을 정의한다. 아래 예제에 regex
클래스를 사용하지만 wregex
도 사용할 수 있다.
정규 표현식의 기능은 템플릿을 통하여 광범위하게 구현된다. 템플릿은 basic_string<char>
유형을 사용한다 (이것은 std::string
과 동등하다). 마찬가지로 OutputIter (출력 반복자)와 BidirConstIter (양방향 상수 반복자)와 같은 총칭 유형이 여러 함수 템플릿과 함께 사용된다. 함수 템플릿은 호출 시간에 건네지는 인자를 보고 실제 유형을 추론한다.
다음은 정규 표현식을 사용할 때 처리하는 단계이다.
regex
객체를 정의하고 변경하는 일이 포함된다.
regex
객체가 정규 표현식을 처리하는 방식은 std::regex_constants
값을 bit_or
로 조합하여 바꿀 수 있다. regex::flag_type
값을 정의한다. 이 regex_constants
는 다음과 같다.
std::regex_constants::awk
:awk(1)의 (POSIX) 정규 표현식 문법을 사용한다 (/\w+/
와 같이 정규 표현식은/
문자로 구분된다. 더 자세한 내용은 각 프로그램의 도움말을 참고하기를 바란다);
std::regex_constants::basic
:기본 POSIX 정규 표현식 문법을 사용한다.
std::regex_constants::collate
:문자 부류에 사용된 문자 범위 연산자(-
)는 로케일을 감지하여 범위를 정의한다 (예를 들어[a-k]
);
std::regex_constants::ECMAScript
:regex
생성자에서 이flag_type
이 기본으로 사용된다. 이 정규 표현식은 변형 ECMAScript 정규 표현식 문법을 사용한다.
std::regex_constants::egrep
:egrep(1)의 (POSIX) 정규 표현식 문법을 사용한다.regex_constants::extended
에서 사용하는 것과 문법이 같지만,'|'
연산자에 대한 대안으로 새줄 문자('\n'
)가 추가되었다.
std::regex_constants::extended
:확장 POSIX 정규 표현식 문법을 사용한다.
std::regex_constants::grep
:grep(1)의 (POSIX) 정규 표현식 문법을 사용한다.regex_constants::basic
에서 사용하는 문법과 같지만,'|'
연산자에 대한 대안으로 새줄 문자('\n'
)가 추가되었다.
std::regex_constants::icase
:목표 문자열에 대소문자를 구분하지 않는다. 예를 들어 정규 표현식A
는a
와A
에 일치한다.
std::regex_constants::nosubs
:부합을 수행할 때 모든 부-표현식은 ((expr)
) 표식이 없는 것으로 취급한다 (?:expr
);
std::regex_constants::optimize
:정규 표현식 부합의 속도를 최적화한다. 정규 표현식의 생성은 약간 속도 저하를 감수한다. 같은 정규 표현식 객체를 자주 사용한다면 이 깃발(플래그)은 목표 텍스트 부합의 속도를 크게 높일 수 있다.
생성자
기본적으로 이동 생성자와 복사 생성자를 사용할 수 있다. 기본 생성자는 유형이 regex::flag_type
인 매개변수 하나를 정의한다. 기본값은 regex_constants::ECMAScript
이다.
regex()
:기본 생성자는 안에 정규 표현식이 없는 regex
객체를 정의한다.
explicit regex(char const *pattern)
:pattern
에서 발견되는 정규 표현식을 담은regex
객체를 정의한다.
regex(char const *pattern, std::size_t count)
:pattern
의 앞쪽부터count
개의 문자 안에서 발견되는regex
객체를 정의한다.
explicit regex(std::string const &pattern)
:pattern
에서 발견되는 정규표현식을 담은regex
객체를 정의한다. 이 생성자는 멤버 템플릿으로 정의된다.basic_string
유형의 인자를 받는다. 비-표준 문자 유형속성과 배당자를 사용할 수도 있다.
regex(ForwardIterator first, ForwardIterator last)
:(정방향) 반복자[first, last)
범위에서 발견되는 정규 표현식을 담은regex
객체를 정의한다. 이 생성자는 멤버 템플릿으로 정의된다. 정방향 반복자 유형을 받는다 (예를 들어 평범한char
포인터). 정규 표현식의 패턴을 정의할 수 있다.
regex(std::initializer_list<Char> init)
:init
리스트에 있는 문자들로부터 정규 표현식을 담은regex
객체를 정의한다.
다음은 몇 가지 예이다.
std::regex re("\\w+"); // 알파벳-숫자의 연속에 부합한다. // 그리고/또는 밑줄 문자 std::regex re{'\\', 'w', '+'} ; // 위와 같음 std::regex re(R"(\w+xxx")", 3); // 위와 같음
멤버 함수
regex &operator=(RHS)
:복사 연산자와 이동 연산자를 사용할 수 있다. 그렇지 않으면 RHS는 다음이 될 수 있다.
- NTBS (
char const *
유형);std::string const &
(또는 호환되는std::basic_string
);std::initializer_list<char>
;
regex &assign(RHS)
:이 멤버는 (선택적인)regex_constants
값을 포함하여,regex
의 생성자와 같은 인자를 받는다.
regex::flag_type flag() const
:현재regex
객체에 활성화되어 있는regex_constants
깃발을 돌려준다. 예를 들어,int main() { regex re; regex::flag_type flags = re.flags(); cout << // 16 0 0을 보여줌 (re.flags() & regex_constants::ECMAScript) << ' ' << (re.flags() & regex_constants::icase) << ' ' << (re.flags() & regex_constants::awk) << ' ' << '\n'; }
flag_type
값의 조합을 생성 시간에 지정하면 지정된 깃발만 설정된다는 것을 주목하라. 예를 들어 re(regex_constants::icase)
를 지정했다면 cout
서술문에 0 1 0
이 보였을 것이다. regex_constants::awk | regex_constants::grep
와 같이 깃발 값을 모순되게 조합하는 것도 가능하다. 그런 식으로 regex
객체를 생성할 수는 있지만 피하는 게 좋다.
locale_type get_loc() const
:현재 regex
객체에 연관된 로케일을 돌려준다.
locale_type imbue(locale_type locale)
:regex
객체의 현재 로케일 설정을locale
로 교체한다. 교체된 로케일을 돌려준다.
unsigned mark_count() const
:regex
객체에 있는 표식 붙은 부-표현식의 갯수를 돌려준다. 예를 들어,int main() { regex re("(\\w+)([[:alpha:]]+)"); cout << re.mark_count() << '\n'; // 출력: 2 }
void swap(regex &other) noexcept
:현재regex
객체를other
와 서로 바꾼다. 자유 함수로도 사용할 수 있다.void swap(regex &lhs, regex &rhs)
이면lhs
와rhs
를 서로 교환한다.
regex
객체를 사용할 수 있고 목표 텍스트를 정규 표현식에 맞추어 보려면 다음 항에 기술되는 함수들을 사용할 수 있다 (18.8.4항):
regex_match
는 단순히 목표 텍스트를 정규 표현식에 맞추어 보고 호출자에게 부합을 발견했는지 못 했는지를 알려준다.
regex_search
도 목표 텍스트를 정규 표현식에 맞추어 본다. 그러나 표식 붙은 부-표현식에 부합한 것들을 열람할 수 있다 (즉, 괄호 두른 정규 표현식);
regex_replace
는 목표 텍스트를 정규 표현식에 맞추어 보고 목표 텍스트에서 부합한 부분들을 또다른 텍스트로 교체한다.
이 함수들은 목표 텍스트와 regex
객체를 (또는 그 객체에 대한 상수 참조를) 제공해야 한다. 부합의 결과를 담기 위해 또다른 인자 std::match_results
객체도 이 함수들에 건넨다.
match_results
클래스를 사용하기 전에 <regex>
헤더를 포함해야 한다.
match_results
객체를 사용하는 예는 18.8.4항에 있다. 이번 항과 다음 항은 주로 참조 목적으로 제공한다.
match_results
의 다양한 전문 버전이 존재한다. 전문 버전은 사용된 regex
클래스의 전문 버전에 일치해야 한다.
예를 들어 정규 표현식이 char const *
로 지정되어 있으면 match_results
전문 버전도 char
const *
값에 작동해야 한다. match_results
의 다양한 전문 버전은 쉽게 기억할 수 있도록 이름이 주어졌다. 그래서 적절한 전문 버전을 쉽게 선택할 수 있다.
match_results
클래스는 다음의 전문 버전이 있다.
cmatch
:match_results<char const *>
를 정의하고,char const *
유형의 반복자를 사용한다.regex(char const *)
전문 버전과 함께 사용해야 한다.
wcmatch
:match_results<wchar_ const *>
를 정의하고,wchar_t const *
유형의 반복자를 사용한다.regex(wchar_t const *)
전문 버전과 함께 사용해야 한다.
smatch
:match_results<std::string::const_iterator>
를 정의하고,std::string::const_iterator
유형의 반복자를 사용한다.regex(std::string const &)
전문 버전과 함께 사용해야 한다.
wsmatch
:match_results<std::wstring::const_iterator>
를 정의하고,std::wstring::const_iterator
유형의 반복자를 사용한다.regex(wstring const &)
전문 버전과 함께 사용해야 한다.
생성자
기본 복사 생성자와 이동 생성자를 사용할 수 있다. 기본 생성자는 Allocator const &
매개변수를 정의한다. 이 매개변수는 기본 배당자로 초기화된다. match_results
클래스의 객체는 regex_match
와 같이 위에 언급된 함수들에 건네어 부합에 관련된 정보를 받는다. 이런 함수들로부터 돌아올 때 match_results
클래스의 멤버를 사용하면 부합 결과를 열람할 수 있다.
멤버 함수
match_results &operator=
:복사 할당 연산자와 이동 할당 연산자를 사용할 수 있다.
std::string const &operator[](size_t idx) const
:부분-부합idx
에 대한 (상수) 참조를 돌려준다.idx
값이 0이면 전체 부합을 참조로 돌려준다.idx >= size()
이면 (아래 참고) 목표 문자열의 빈 부분-범위를 참조로 돌려준다.ready()
멤버가 (아래 참고)false
를 돌려준다면 이 멤버의 행위는 정의되어 있지 않다.
Iterator begin() const
:첫 번째 부분-부합을 가리키는 반복자를 돌려준다.Iterator
는const match_results
객체에 대한 상수 반복자이다.
Iterator cegin() const
:첫 번째 부분-부합을 가리키는 반복자를 돌려준다. Iterator
는 상수 반복자이다.
Iterator cend() const
:마지막 부분-부합 다음을 가리키는 반복자를 돌려준다. Iterator
는 상수 반복자이다.
Iterator end() const
:마지막 부분-부합 다음을 가리키는 반복자를 돌려준다.Iterator
는const match_results
객체를 가리키는 상수 반복자이다.
ReturnType format(Parameters) const
:이 멤버는 상당히 광범위한 기술을 요하기 때문에, 현재 개관의 흐름을 깰 가능성이 있다. 이 멤버는 regex_replace
함수와 조합하여 사용된다. 그러므로 함수 부분에 더 자세하게 다루겠다 (18.8.4.5목);
allocator_type get_allocator() const
:객체의 배당자를 돌려준다.
bool empty() const
:match_results
객체에 부합이 없으면true
를 돌려준다 (단순히 기본 생성자를 사용한 후에도 반환된다). 그렇지 않으면false
를 돌려준다.
int length(size_t idx = 0) const
:부분-부합idx
의 길이를 돌려준다. 기본으로 전체 부합의 길이를 돌려준다.idx >= size()
이면 (아래 참고) 0이 반환된다.
size_type max_size() const
: match_results
객체에 담을 수 있는 부분-부합의 최대 갯수를 돌려준다. 이것은 구현에 독립적인 상수 값이다.
int position(size_t idx = 0) const
:목표 텍스트에서 부분-부합idx
의 첫 번째 문자의 오프셋을 돌려준다. 기본으로 전체 부합의 첫 문자의 위치가 반환된다.idx >= size()
이면 (아래 참고) -1이 반환된다.
std::string const &prefix() const
:전체 부합의 첫 번째 문자 앞에서 끝나는 부-문자열에 대하여 (상수) 참조를 돌려준다.
bool ready() const
:기본 생성된match_results
객체로부터 부합 결과를 얻을 수 없다. 언급한 부합 함수 중 하나로부터 부합 결과를 받는다. 부합 결과가 있으면true
를 돌려주고 그렇지 않으면false
를 돌려준다.
size_type size() const
:부분-부합의 갯수를 돌려준다. 정규 표현식이(abc)|(def)
이고 목표가defcon
이면 세 개의 부분 부합이 보고된다. 전체 부합(def)이 하나(abc)
에 대하여 빈 텍스트가 둘 그리고(def)
표식붙은 부분 표현에 대하여def
가 셋이다. 주의하라. 빈도 지정자를 사용하면 마지막 부합만 세어 보고한다. 패턴이(a|b)+
이고 목표가aaab
이라면 부분-부합이 두 개 보고된다. 전체 부합aaab
와 마지막 부합 (b
)가 보고된다.
std::string str(size_t idx = 0) const
:부분-부합idx
를 정의한 문자들을 돌려준다. 기본 값으로 이것은 전체 부합이다.idx >= size()
이면 (아래 참고) 빈 문자열이 반환된다.
std::string const &suffix() const
:전체 부합의 마지막 문자 다음부터 시작하는 목표 텍스트의 부-문자열에 대하여 (상수) 참조를 돌려준다.
void swap(match_results &other) noexcept
:현재match_results
객체를other
와 서로 바꾼다. 자유 함수로도 사용할 수 있다. 다음과 같이lhs
와rhs
를 서로 교환한다.void swap(match_results &lhs, match_results &rhs)
<regex>
헤더를 포함해야 한다.
목표 텍스트를 정규 표현식에 맞추어 볼 수 있는 함수 가족이 세 가지 있다. 각 함수와 더불어 match_results::format
멤버는 마지막 인자가 std::regex_constants::match_flag_type
매개변수이다 (다음 목 참고). 이 매개변수는 기본 값이 regex_constants::match_default
이다. 이 값을 사용하여 정규 표현식과 사용중인 부합 과정을 세심하게 조율할 수 있다. 이 마지막 매개변수는 정규 표현식 부합 함수나 format
멤버에 명시적으로 언급되지 않는다. 함수 가족 세 가지는 다음과 같다.
bool std::regex_match(Parameters)
:
이 함수 가족은 정규 표현식을 목표 텍스트에 맞추어 본다. 정규 표현식이 전체 텍스트에 부합할 경우에만true
를 돌려준다. 그렇지 않으면false
가 반환된다. 사용가능한 중복정의regex_match
함수의 개관은 18.8.4.2목을 참고하라;
bool std::regex_search(Parameters)
:
이 함수 가족도 정규 표현식을 목표 텍스트에 맞추어 본다. 이 함수는 정규 표현식이 목표 텍스트의 부분-문자열에 부합하면 참을 돌려준다. 그렇지 않으면false
를 돌려준다. 사용 가능한 중복정의regex_search
함수는 아래에 개관한다.
ReturnType std::regex_replace(Parameters)
:이 함수 가족은 텍스트를 변경한다. 목표 문자열의 문자와regex
객체 그리고 형식화 문자열을 사용한다. 이 멤버는 18.8.4.4목에 논의한match_results::format
멤버의 기능을 많이 닮았다.
regex_replace
다음에 match_results::format
멤버를 사용할 수 있다 (18.8.4.4목). regex_replace
를 먼저 다룬다.
format
멤버와 모든 정규 표현식 부합 함수는 마지막에 regex_constants::match_flag_type
인자를 받는다. 이 인자는 비트-마스크 유형으로서 bit_or
연산자를 사용할 수 있다. 모든 format
멤버는 기본으로 match_default
인자를 지정한다.
match_flag_type
열거체는 다음 값들을 정의한다 (아래에 `[first, last)
' 범위는 부합중인 목표 문자열을 가리킨다).
format_default
(비트-마스크 값은 아니지만 0이 기본 값이다). 이것만 지정하면 ECMAScript 규칙을 사용하여 std::regex_replace
에 문자열을 구성한다 ;
format_first_only
: std::regex_replace
첫 번째 일치만 교체한다.
format_no_copy
: 부합하지 않은 문자열은 std::regex_replace
가 출력으로 건네지 않는다.
format_sed
: std::regex_replace
에 문자열을 구성하는 데 POSIX sed(1) 규칙이 사용된다.
match_any
: 부합이 여러 개이면 모조리 결과로 받는다.
match_continuous
: 부-연속열은 first
에서 시작할 경우에만 부합한다.
match_not_bol
: [first, last)
범위의 첫 번째 문자는 평범한 문자로 취급한다. ^
는 [first, first)
범위에 부합하지 않는다.
match_not_bow
: \b
는 [first, first)
에 부합하지 않는다.
match_default
(비트-마스크 값은 아니지만 0과 동등하다): 마지막 인자의 기본 값으로서 정규 표현식 부합 함수와 match_results::format
멤버에 건네진다. std::regex_replace
안의 문자열을 구성하는 데 ECMAScript 규칙이 사용된다.
match_not_eol
: [first, last)
에서 마지막 문자는 평범한 문자로 취급한다. $
는 [last,last)
범위에 부합하지 않는다.
match_not_eow
: \b
는 [last, last)
에 부합하지 않는다.
match_not_null
: 빈 연속열은 부합한 것으로 간주하지 않는다.
match_prev_avail
: --first
는 유효한 문자 위치를 참조한다.
이것이 지정되면 match_not_bol
과 match_not_bow
는 무시된다.
std::regex_match
함수는 regex
인자에 정의된 정규 표현식이 목표 텍스트에 완전히 부합하면 true
를 돌려준다. 이것은 match_results::prefix
와 match_results::suffix
가 반드시 빈 문자열을 돌려주어야 한다는 뜻이다. 그러나 부-표현식을 정의하는 것은 문제가 없다.
이 함수의 중복정의 변형은 다음과 같다.
bool regex_match(BidirConstIter first, BidirConstIter last,
std::match_results &results,
std::regex const &re)
:BidirConstIter
는 양방향 상수 반복자이다. 범위[first, last)
는 목표 텍스트를 정의한다. 부합 결과는results
에 반환된다. 반복자의 유형은 사용된match_results
의 유형과 반드시 일치해야 한다. 예를 들어 반복자가 유형이char const *
이면cmatch
를 사용해야 한다. 그리고 반복자가 유형이string::const_iterator
이면smatch
를 사용해야 한다. 이 함수의 다른 중복정의 버전에도 비슷한 상응성이 요구된다.
bool regex_match(BidirConstIter first, BidirConstIter last,
std::regex const &re)
:이 함수는 이전의 함수처럼 작동한다. 그러나 부합 결과를 match_results
객체에 돌려주지 않는다.
bool regex_match(char const *target,
std::match_results &results, std::regex const &re)
:이 함수는 첫 번째 중복정의 변형처럼 행동한다. target
안의 문자들을 목표 텍스트로 사용한다.
bool regex_match(char const *str, std::regex const &re)
:이 함수는 이전 함수처럼 행위한다. 그러나 부합 결과를 돌려주지 않는다.
bool regex_match(std::string const &target,
std::match_results &results, std::regex const &re)
:이 함수는 첫 번째 중복정의의 변형처럼 행위한다. target
안의 문자들을 목표 텍스트로 사용한다.
bool regex_match(std::string const &str, std::regex const &re)
:이 함수는 이전 함수처럼 행위한다. 그러나 부합 결과를 돌려주지 않는다.
bool regex_match(std::string const &&, std::match_results &,
std::regex &) = delete
(regex_match
함수는 임시 string
객체를 목표 문자열로 받지 않는다. 이렇게 하면 match_result
인자에 무효한 문자열 반복자가 생길 수 있기 때문이다.)
[[:alpha:]]
) 정규 표현식은 목표 텍스트에 부합한다 (argv[1]
로 제공). 숫자들은 부-표현식 1로 열람할 수 있다.
#include <iostream> #include <regex> using namespace std; int main(int argc, char const **argv) { regex re("(\\d{5})[[:alpha:]]+"); cmatch results; if (not regex_match(argv[1], results, re)) cout << "No match\n"; else cout << "size: " << results.size() << ": " << results.str(1) << " -- " << results.str() << '\n'; }
regex_match
와 다르게 std::regex_search
함수는 regex
인자에 정의된 정규 표현식이 목표 텍스트의 한 부분에 부합하면 true
를 돌려준다.
이 함수의 중복정의 변형은 다음과 같다.
bool regex_search(BidirConstIter first, BidirConstIter last,
std::match_results &results,
std::regex const &re)
:BidirConstIter
는 양방향 상수 반복자이다. 범위[first, last)
는 목표 텍스트를 정의한다. 부합 결과는results
에 반환된다. 반복자의 유형은 사용된match_results
의 유형에 일치해야 한다. 예를 들어 반복자의 유형이char const *
라면cmatch
를 사용해야 한다. 그리고 반복자의 유형이string::const_iterator
이라면smatch
를 사용해야 한다. 이 함수의 다른 중복정의 버전에도 비슷한 상응성이 요구된다.
bool regex_search(BidirConstIter first, BidirConstIter last,
std::regex const &re)
:이 함수는 이전 함수처럼 행위한다. 그러나 부합의 결과를 match_results
객체에 돌려주지 않는다.
bool regex_search(char const *target,
std::match_results &results, std::regex const &re)
:이 함수는 첫 번째 중복정의의 변형처럼 행위한다. target
안의 문자들을 목표 텍스트로 사용한다.
bool regex_search(char const *str, std::regex const &re)
:이 함수는 이전 함수처럼 행위한다. 그러나 부합 결과를 돌려주지 않는다.
bool regex_search(std::string const &target,
std::match_results &results, std::regex const &re)
:이 함수는 첫 번째 중복정의의 변형처럼 행위한다. target
안의 문자들을 목표 텍스트로 사용한다.
bool regex_search(std::string const &str, std::regex const &re)
:이 함수는 이전 함수처럼 행위한다. 그러나 부합 결과를 돌려주지 않는다.
bool regex_search(std::string const &&, std::match_results &,
std::regex &) = delete
:이 함수는 임시의string
객체를 목표 문자열로 받지 않는다. 이렇게 하면match_result
인자에 무효한 문자열 반복자가 생길 수 있기 때문이다.
regex_search
의 사용법을 보여준다.
1: #include <iostream> 2: #include <string> 3: #include <regex> 4: 5: using namespace std; 6: 7: int main() 8: { 9: while (true) 10: { 11: cout << "Enter a pattern or plain Enter to stop: "; 12: 13: string pattern; 14: if (not getline(cin, pattern) or pattern.empty()) 15: break; 16: 17: regex re(pattern); 18: while (true) 19: { 20: cout << "Enter a target text for `" << pattern << "'\n" 21: "(plain Enter for the next pattern): "; 22: 23: string text; 24: if (not getline(cin, text) or text.empty()) 25: break; 26: 27: smatch results; 28: if (not regex_search(text, results, re)) 29: cout << "No match\n"; 30: else 31: { 32: cout << "Prefix: " << results.prefix() << "\n" 33: "Match: " << results.str() << "\n" 34: "Suffix: " << results.suffix() << "\n"; 35: for (size_t idx = 1; idx != results.size(); ++idx) 36: cout << "Match " << idx << " at offset " << 37: results.position(idx) << ": " << 38: results.str(idx) << '\n'; 39: } 40: } 41: } 42: }
format
멤버는 match_results
클래스의 약간 복잡한 멤버 함수이다. 앞서 regex_search
함수로 정규 표현식에 부합한 텍스트를 변경할 수 있다. 복잡하기도 하고 (regex_replace
라는) 또다른 정규 표현식 처리 함수가 비슷한 기능을 제공하기 때문에, 먼저 여기에 논의한다. 바로 이어서 regex_replace
함수를 연구한다.
format
멤버는 match_results
객체에 담긴 (부분-) 부합에 작동한다. 형식화 문자열을 사용하고 텍스트를 만들어낸다. 여기에서 ($&
와 같은) 형식 지정자들은 원래 제공된 목표 텍스트에 부합한 부분들로 교체된다. 게다가 format
멤버는 모든 표준 C 피신 연속열을 인지한다 (예, \n
). format
멤버는 원래 목표 텍스트를 변형한 텍스트를 만들어 낸다.
이를테면 results
가 match_results
객체이고 match[0]
이 (완전히 부합한 텍스트) `hello world
'와 같을 경우에 형식화 문자열 this is [$&]
를 가지고 format
을 호출하면 텍스트 this is [hello world]
를 출력한다. 이 형식화 문자열에 $&
를 지정한 것을 눈여겨보라. 이것은 형식화 지정자의 한 예이다. 다음은 지원되는 형식화 지정자를 모두 개관한 것이다.
$`
: prefix
멤버가 반환하는 텍스트에 상응한다. 원래 목표 텍스트에서 완전히 부합한 텍스트의 첫 번째 문자 전까지의 모든 문자;
$&
: 완전히 부합한 텍스트에 상응한다 (즉, match_results::str
멤버가 돌려주는 텍스트);
$n
: (여기에서 n
은 자연수이다): operator[](n)
이 돌려주는 텍스트에 상응한다.
$'
: suffix
멤버가 돌려주는 텍스트에 상응한다. 원래 목표 문자열에서 완전히 부합한 텍스트의 마지막 문자 다음의 모든 문자;
$$
: $
문자 하나에 상응한다.
format
멤버는 네 개의 중복정의 버전이 있다. 모든 중복정의 버전은 마지막에 regex_constants::match_flag_type
매개변수를 정의한다. 이 매개변수는 기본으로 match_default
로 초기화된다. 이 마지막 매개변수는 다음의 format
멤버에 명시적으로 언급되지 않는다.
format
멤버의 사용법을 더 보여주기 위해 다음 코드를 실행했다고 가정하자.
1: regex re("([[:alpha:]]+)\\s+(\\d+)"); // 알파벳 공백 숫자 2: 3: smatch results; 4: string target("this value 1024 is interesting"); 5: 6: if (not regex_search(target, results, re)) 7: return 1;
regex_search
(줄 6) 함수를 호출한 후, 정규 표현식의 부합 결과는 줄 3에 정의된 match_results
객체에 반환된다.
앞의 두 중복정의 format
함수는 형식화된 텍스트를 쓰기 위하여 출력-반복자를 기대한다. 이런 중복정의 멤버는 마지막에 출력 반복자를 돌려준다. 이 반복자는 이전에 썼던 문자 바로 다음 위치를 가리킨다.
OutputIter format(OutputIter out, char const *first, char const *last) const
:[first, last)
범위의 문자들이match_results
객체에 저장된 부-표현식에 적용된다. 그리고 결과 문자열이out
에 삽입된다. 다음 중복정의 버전과 함께 예를 제공한다.
OutputIter format(OutputIter out, std::string const &fmt) const
:fmt
의 내용이match_results
객체에 저장된 부-표현식에 적용된다. 그리고 결과 문자열은out
에 삽입된다. 다음 코드는 값 1024를cout
에 삽입한다 (fmt
는 반드시std::string
이어야 함을 주의하라. 그러므로 명시적으로string
생성자를 사용한다):results.format(ostream_iterator<char>(cout, ""), string("$2"));
나머지 두 개의 중복정의 format
멤버는 형식화 문자열을 정의하는 std::string
이나 NTBS를 기대한다. 두 멤버 모두 형식화된 텍스트를 담은 std::string
을 돌려준다.
std::string format(std::string const &fmt) const
std::string format(char const *fmt) const
string
을 어떻게 얻는지 보여준다. 여기에서 이전에 획득한 match_results
객체에 포함된 첫 번째 표식과 두 번째 표식이 붙은 부-표현식의 순서가 서로 바뀐다.
string reverse(results.format("$2 and $1"));
std::regex_replace
함수 가족은 정규 표현식으로 문자열을 교체한다.
기능은 이전 항에서 논의한 match_results::format
멤버를 많이 닮았다. 다음의 중복정의 변형을 사용할 수 있다.
OutputIt regex_replace(OutputIter out,
BidirConstIter first, BidirConstIter last,
std::regex const &re, std::string const &fmt)
:OutputIter
은 출력 반복자이다.BidirConstIter
는 양방향 상수 반복자이다.이 함수는 반복자
[out, retvalue)
범위에서 교체된 텍스트를 돌려준다. 여기에서out
은regex_replace
에 첫 인자로 건네어지는 출력 반복자이다.retvalue
는regex_replace
가 돌려준 출력 반복자이다.반복자
[first, last)
범위의 텍스트를re
에 저장된 정규 표현식에 맞추어 본다. 부합하지 않으면 목표 텍스트는 그대로out
에 복사된다. 부합하면:
- 먼저, 부합 결과의 접두부가
out
에 복사된다. 접두부는 완전히 부합된 텍스트의 제일 첫 문자까지의 앞쪽의 모든 문자와 같다.- 다음, 부합된 텍스트는
fmt
형식화 문자열의 내용으로 교체된다. 형식화 문자열에 이전 목에 기술한 형식 지정자를 사용할 수 있다 (18.8.4.4목). 그리고 교체된 텍스트는out
에 복사된다.- 마지막으로, 부합 결과의 접미부는
out
에 복사된다. 접미부는 목표 텍스트에서 부합된 텍스트의 마지막 문자 다음의 모든 문자와 같다.regex_replace
의 작동 방식을 다음 예제에 보여준다.1: regex re("([[:alpha:]]+)\\s+(\\d+)"); // 기호 공백 숫자 2: 3: string target("this value 1024 is interesting"); 4: 5: regex_replace(ostream_iterator<char>(cout, ""), target.begin(), 6: target.end(), re, string("$2"));줄 5에서
regex_replace
가 호출된다. 그의 형식화 문자열은 단순히$2
일 뿐이며, 목표 텍스트의 1024에 부합한다. 접두부는 단어value
에서 끝나고 접미부는 1024를 넘어서 시작한다. 그래서 줄 5의 서술문은 다음 텍스트를this 1024 is interesting표준 출력 스트림에 삽입한다.
OutputIt regex_replace( OutputIter out, BidirConstIter first,
BidirConstIter last, std::regex const &re, char const *fmt)
:이 변형은 첫 변형과 똑 같이 행위한다. 위의 예제에서string("$2")
대신에"$2"
를 사용하면 이 변형을 사용할 수 있을 것이다.
std::string regex_replace(std::string const &str,
std::regex const &re, std::string const &fmt)
:이 변형은 변경된 텍스트를 담은std::string
을 돌려주고 목표 텍스트를 담은std::string
을 기대한다. 그것 말고는 첫 번째 변형과 똑 같이 행위한다. 이 중복정의 변형을 사용하려면 위의 예제에서 5번 줄의 서술문을 다음 서술문으로 바꾸고string result
를 초기화하면 된다.string result(regex_replace(target, re, string("$2")));
std::string regex_replace(std::string const &str,
std::regex const &re, char const *fmt)
:위의 서술문에서string("$2")
를"$2"
으로 변경한 후에 이 변형을 사용하면 정확하게 이전 변형과 똑 같이 행위한다.
std::string regex_replace(char const *str,
std::regex const &re, std::string const &fmt)
:이 변형은 목표 텍스트를 가리키기 위해 char const *
를 사용한다. 한 가지 변형만 빼면 정확하게 이전 변형과 똑 같이 행위한다.
std::string regex_replace(char const *str,
std::regex const &re, char const *fmt)
:이 변형도 목표 텍스트를 가리키기 위해 char const *
를 사용한다. 한 가지 변형만 빼면 역시 이전 변형과 정확하게 똑 같이 행위한다.
<random>
헤더를 포함해야 한다.
STL은 여러 표준 수학적 (통계적) 분포를 제공한다. 프로그래머는 통계적 분포도에서 무작위로 선택된 값들을 얻을 수 있다.
통계적 분포도는 난수 발생 객체를 제공할 필요가 있다. C 표준 라이브러리에 포함된 전통적인 rand
함수를 확장하여 여러 난수 엔진 객체가 제공된다.
이 엔진들은 의사-난수를 만들어 낸다. 이를 통계 분포에서 처리하여 지정한 분포도로부터 무작위로 선택된 값들을 얻는다.
STL은 다양한 통계 분포를 제공하지만 기능이 많이 부족하다. 확률 밀도 함수나 누적 분포 함수는 현재 STL에서 지원하지 않는다. 그렇지만 이 함수들은 다른 라이브러리에서 얻을 수 있다. 예를 들어 boost 수학 라이브러리가 있다
(특히: http://www.boost.org/doc/libs/1_44_0/libs/math/doc/sf_and_dist/html/index.html).
다양한 통계 분포에 관하여 수학적 특징을 논의하는 것은 이 책의 범위를 벗어난다. 관심이 있는 독자는 수학 교과서를 참고하시기를 바란다 (예, Stuart and Ord's (2009) Kendall's Advanced Theory of Statistics, Wiley) 또는 웹에서 http://en.wikipedia.org/wiki/Bernoulli_distribution를 참고하라.
클래스 템플릿 | 정수/부동소수점수 | 품질 | 속도 | 크기 |
linear_congruential_engine | 정수 | 보통 | 보통 | 1 |
subtract_with_carry_engine | 정수/소수 | 보통 | 빠름 | 25 |
mersenne_twister_engine | 정수 | 좋음 | 빠름 | 624 |
linear_congruential_engine
난수 엔진은 다음을 계산한다.
value
i+1 = OPENPAa * value
i + c) % m
템플릿 인자를 기대한다. 각각 생성된 난수 값을 담을 데이터 유형과 배율 a
와 양의 상수 c
그리고 나머지 연산(modulo) 값 m
을 기대한다. 예제:
linear_congruential_engine<int, 10, 3, 13> lincon;
linear_congruential
엔진에 씨 값을 주려면 생성자에 예를 들어 lincon(time(0))
과 같이 씨-인자를 제공하면 된다.
subtract_with_carry_engine
난수 엔진은 다음을 계산한다.
value
i = (value
i-s - value
i-r - carry
i-1) % m
템플릿 인자를 기대한다. 발생된 난수 값을 담을 데이터 유형과 나머지 연산(modulo)의 값 m
그리고 음의 상수 s
와 r
을 기대한다. 예제:
subtract_with_carry_engine<int, 13, 3, 13> subcar;
subtract_with_carry_engine
엔진에 씨값을 주려면 생성자에 subcar(time(0))
와 같이 씨-인자를 제공하면 된다.
미리 정의된 mersenne_twister_engine mt19937
엔진을 아래 예제에 사용한다 (<random>
헤더 파일에 typedef
로 미리 정의되어 있다.). `mt19937 mt
'로 생성하거나 아니면 mt19937 mt(time(0))
과 같이 생성자의 인자에 씨값을 건네줄 수 있다 (이 엔진은 주기가 219937 - 1이다). mersenne_twister_engine
을 초기화하는 다른 방법은 이 책의 범위를 벗어난다 (다음 참고: Lewis et al. ( Lewis, P.A.W., Goodman, A.S., and Miller, J.M. (1969), A pseudorandom number generator for the System/360, IBM Systems Journal, 8, 136-146.) (1969)).
난수 엔진은 seed
멤버를 호출해 씨 값을 뿌릴 수도 있다. seed
멤버는 unsigned long
값이나 발생자 함수를 받는다 (다음과 같이: lc.seed(time(0)), lc.seed(mt)
).
난수 엔진은 min
멤버와 max
멤버를 제공한다. 각각 최소값과 최대값(포함)을 돌려준다. 범위를 축소해야 한다면 엔진을 범위에 맞게 함수나 클래스 안에 내포시킬 수 있다.
RNG
(Random Number Generator)는 난수 발생기를 뜻한다. URNG
(Uniform Random Number Generator)는 균등 난수 발생기를 나타낸다. 각 분포마다 모수를 담는 구조체(struct param_type
)가 정의되어 있다. param_type
구조체의 조직은 실제 분포에 따라 다르다 (그에 따라 기술한다).
모든 분포는 다음 멤버를 제공한다 (result_type은 분포가 돌려주는 값의 유형 이름을 참조한다):
result_type max() const
result_type min() const
param_type param() const
param_type
구조체를 돌려준다.
void param(const param_type ¶m)
분포의 매개변수를 재정의한다.
void reset():
캐쉬된 값을 모두 소거한다.
모든 분포는 다음 연산자를 지원한다 (분포-이름은 normal_distribution
과 같이 해당 분포의 이름으로 교체해야 한다.).
template<typename URNG> result_type operator()(URNG &urng)
urng
함수객체는 균등한 무작위 분포로부터 다음 난수를 뽑아 돌려준다.
template<typename URNG> result_type operator()
(URNG &urng, param_type ¶m)
param
구조체에 주어진 매개변수로 초기화한 통계 분포로부터 다음 난수를 돌려준다. urng
함수객체는 균등 무작위 분포로부터 다음 난수를 뽑아 돌려준다.
std::istream &operator>>(std::istream &in,
distribution-name &object):
std::istream
으로부터 추출된다.
std::ostream &operator<<(std::ostream &out,
distribution-name const &bd):
std::ostream
에 삽입된다.
다음 예제는 어떻게 분포를 사용하는지 보여준다. (normal_distribution
) 분포의 이름을 원하는 분포의 이름으로 바꾸면 다른 분포로 전환된다. 분포는 모두 모수가 있다. 예를 들어 정규 분포라면 표준 편차와 평균이 모수이다. 모수마다 모두 기본 값이 있다. 모수의 이름은 분포마다 다르며 개별적으로 아래에 언급한다. 각 분포마다 모수를 돌려주거나 설정하는 멤버가 있다.
대부분의 분포는 클래스 템플릿으로 정의되어 있다. 그래서 데이터 유형을 함수의 반환 유형에 지정할 필요가 있다. 빈 템플릿 매개변수 유형을 지정하면 (<>
) 기본 유형이 자동으로 지정될 것이다. 기본 유형은 (반환 값의 유형이 실수라면) double
이거나 아니면 (반환 값의 유형이 정수라면) int
이다. 분포가 템플릿 클래스로 정의되어 있지 않으면 템플릿 매개변수 유형을 지정하지 말아야 한다.
다음은 통계 분포의 사용법을 보여주는 예이다. 정규 분포를 적용한다.
#include <iostream> #include <ctime> #include <random> using namespace std; int main() { std::mt19937 engine(time(0)); std::normal_distribution<> dist; for (size_t idx = 0; idx < 10; ++idx) std::cout << "a random value: " << dist(engine) << "\n"; cout << '\n' << dist.min() << " " << dist.max() << '\n'; } /* 출력 a random value: 0.810857 a random value: 0.154001 a random value: -0.740865 a random value: -0.196818 a random value: -0.293477 a random value: -2.15427 a random value: 0.614848 a random value: 0.96521 a random value: -0.10051 a random value: -0.616091 -1.79769e+308 1.79769e+308 */
bernoulli_distribution
는 어떤 p
확률로 논리적 (불리언) 참 값을 생성한다. 한 번의 시행이라면 이항 분포와 동등하다 (18.9.2.2목).
베르누이 분포는 클래스 템플릿으로 정의되어 있지 않다.
정의된 유형:
typedef bool result_type; struct param_type { explicit param_type(double prob = 0.5); double p() const; // prob를 돌려준다. };
생성자와 멤버:
bernoulli_distribution(double prob = 0.5)
prob
의 확률로 true
를 돌려준다.
double p() const
prob
를 돌려준다.
result_type min() const
false
를 돌려준다.
result_type max() const
true
를 돌려준다.
binomial_distribution<IntType = int>
는 연속적으로 n
회의 독립 시행에서 성공할 확률을 결정한다. 각각의 시행마다 p
의 확률로 성공한다.
템플릿 유형의 매개변수 IntType
은 생성된 난수 값의 유형을 정의한다. 이 유형은 반드시 정수이어야 한다.
정의된 유형:
typedef IntType result_type; struct param_type { explicit param_type(IntType trials, double prob = 0.5); IntType t() const; // 시행 횟수를 돌려준다. double p() const; // 확률을 돌려준다. };
생성자와 멤버 그리고 예제:
binomial_distribution<>(IntType trials = 1, double prob = 0.5)
trials
회의 시행에 대하여 이항 분포를 생성한다. 각각 prob
의 확률로 성공한다.
binomial_distribution<>(param_type const ¶m)
param
구조체에 저장된 값에 맞추어 이항 분포를 구성한다.
IntType t() const
trials
시행 횟수를 돌려준다.
double p() const
prob
확률을 돌려준다.
result_type min() const
result_type max() const
trials
시행 횟수를 돌려준다.
cauchy_distribution<RealType = double>
는 정규 분포와 비슷하게 생겼다. 그러나 코시 분포는 꼬리가 더 두껍다. 정규 분포를 가정하는 가설을 검정할 때, 코시 분포로 생성된 데이터에 테스트를 해보면 정규분포에 비해 꼬리가 두꺼울수록 테스트가 민감하게 반응하는 것을 볼 수 있다.
코시 분포의 중위값과 표준 편차는 정의되어 있지 않다.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType a = RealType(0), RealType b = RealType(1)); double a() const; double b() const; };
생성자와 멤버:
cauchy_distribution<>(RealType a = RealType(0),
RealType b = RealType(1))
a
와 b
모수로 코시 분포를 생성한다.
cauchy_distribution<>(param_type const ¶m)
param
구조체에 저장된 값에 맞게 코시 분포를 생성한다.
RealType a() const
a
를 돌려준다.
RealType b() const
b
를 돌려준다.
result_type min() const
result_type
값을 돌려준다.
result_type max() const
result_type
유형의 가장 큰 값을 돌려준다.
n
인 chi_squared_distribution<RealType = double>
분포는 n
개의 독립적인 표준 정규 무작위 변수들의 제곱을 합한 분포이다.
카이 제곱 분포의 모수 n
은 정수 값이지만 반드시 정수일 필요는 없다. 카이 제곱 분포는 실제 인자를 취하는 (exp
와 Gamma
) 함수의 관점에서 정의되기 때문이다 (Gnu g++
컴파일러 배포본이 제공하는 <bits/random.h>
헤더 파일에 있는 공식들을 참고하라.).
카이 제곱 분포는 관측된 분포가 실험적 분포에 잘 맞는지 테스트할 때 사용된다.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType n = RealType(1)); RealType n() const; };
생성자와 멤버:
chi_squared_distribution<>(RealType n = 1)
chi_squared_distribution<>(param_type const ¶m)
param
구조체에 저장된 값에 맞게 카이 제곱 분포를 생성한다.
IntType n() const
result_type min() const
result_type max() const
result_type
의 최대 값을 돌려준다.
extreme_value_distribution<RealType = double>
는 베이불 분포와 관련되어 있다. 그리고 통계 모델에 사용된다. 여기에서 관심의 대상인 변수는 양수 또는 음수 값을 취할 수 있는 수 많은 무작위 요소 중 가장 작은 값이다.
모수가 두 개이다. 위치 모수 a
와 배율 모수 b
가 있다. 다음도 참고하라
http://www.itl.nist.gov/div898/handbook/apr/section1/apr163.htm
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType a = RealType(0), RealType b = RealType(1)); RealType a() const; // 위치 모수 RealType b() const; // 배율 모수 };
생성자와 멤버:
extreme_value_distribution<>(RealType a = 0, RealType b = 1)
a
와 b
모수로 극단 값 분포를 생성한다.
extreme_value_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 극단 값 분포를 생성한다.
RealType a() const
RealType stddev() const
result_type min() const
result_type
유형의 가장 작은 양의 값을 돌려준다.
result_type max() const
result_type
유형의 최대 값을 돌려준다.
exponential_distribution<RealType = double>
는 동질적인 포아송 분포로 모델링할 수 있는 사건 사이의 길이를 기술하기 위해 사용된다. 기하 분포의 연속적인 형태로 해석할 수 있다.
모수 prob
는 분포의 람다 매개변수를 정의한다. rate 매개변수를 호출한다. 기대 값과 표준 편차는 둘 다 1 / lambda
이다.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType lambda = RealType(1)); RealType lambda() const; };
생성자와 멤버:
exponential_distribution<>(RealType lambda = 1)
lambda
모수로 지수 분포를 생성한다.
exponential_distribution<>(param_type const ¶m)
param
구조체에 정의된 값들에 맞게 지수 분포를 생성한다.
RealType lambda() const
lambda
를 돌려준다.
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
fisher_f_distribution<RealType = double>
는 변량 분석과 같은 통계 방법에 주로 사용된다. 두 개의 카이-제곱 분포를 나눈 결과로 나온 분포이다.
두 개의 모수가 특징인데, 각각 두 개의 카이-제곱 분포의 자유도이다.
모수 n
은 정수 값이지만 반드시 정수일 필요는 없다. 피셔 F 분포가 카이-제곱 분포로부터 생성되기 때문인데 이 분포가 받는 모수는 정수가 아니다 (18.9.2.4목).
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType m = RealType(1), RealType n = RealType(1)); RealType m() const; // 나누는 수의 자유도 RealType n() const; // 나뉘는 수의 자유도 };
생성자와 멤버:
fisher_f_distribution<>(RealType m = RealType(1),
RealType n = RealType(1))
fisher_f_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 fisher_f 분포를 생성한다.
RealType m() const
RealType n() const
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
gamma_distribution<RealType = double>
는 정규 분포에 맞지 않는 데이터와 작업할 때 사용된다. 기다리는 시간을 모델링하는 데 많이 사용된다.
모수가 alpha
와 beta
두 개이다. 기대 값은 alpha * beta
이고 표준 편차는 alpha * beta
2이다.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType alpha = RealType(1), RealType beta = RealType(1)); RealType alpha() const; RealType beta() const; };
생성자와 멤버:
gamma_distribution<>(RealType alpha = 1, RealType beta = 1)
alpha
와 beta
로 감마 분포를 생성한다.
gamma_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 감마 분포를 생성한다.
RealType alpha() const
alpha
를 돌려준다.
RealType beta() const
beta
를 돌려준다.
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
geometric_distribution<IntType = int>
은 (18.9.2.1목) 처음으로 성공할 때까지 필요한 베르누이 시행의 횟수를 모델링하기 위해 사용된다.
모수로 prob
가 있고 개별 베르누이 시행의 성공 확률을 나타낸다.
정의된 유형:
typedef IntType result_type; struct param_type { explicit param_type(double prob = 0.5); double p() const; };
생성자와 멤버 그리고 예제:
geometric_distribution<>(double prob = 0.5)
prob
확률로 성공하는 베르누이 시행에 대하여 기하 분포를 생성한다.
geometric_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 기하 분포를 생성한다.
double p() const
prob
를 돌려준다.
param_type param() const
param_type
구조체를 돌려준다.
void param(const param_type ¶m)
모수를 재정의한다.
result_type min() const
0
);
result_type max() const
template<typename URNG> result_type operator()(URNG &urng)
template<typename URNG> result_type operator()
(URNG &urng, param_type ¶m)
param
구조체로 초기화된 기하 분포로부터 다음 난수를 돌려준다.
#include <iostream> #include <ctime> #include <random> int main() { std::linear_congruential_engine<unsigned, 7, 3, 61> engine(0); std::geometric_distribution<> dist; for (size_t idx = 0; idx < 10; ++idx) std::cout << "a random value: " << dist(engine) << "\n"; std::cout << '\n' << dist.min() << " " << dist.max() << '\n'; }
lognormal_distribution<RealType = double>
는 로그를 씌우면 정상 분포인 확률 변수의 확률 분포이다. 확률 변수 X
가 정규 분포이면, Y = e
X는 로그-정규 분포이다.
모수가 m과 s 두 개이고 각각 ln(X)
의 평균과 표준 편차를 나타낸다.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType m = RealType(0), RealType s = RealType(1)); RealType m() const; RealType s() const; };
생성자와 멤버:
lognormal_distribution<>(RealType m = 0, RealType s = 1)
m
과 s
인 확률 변수에 대하여 로그-정규 분포를 생성한다.
lognormal_distribution<>(param_type const ¶m)
param
에 저장된 값들에 맞게 로그-정규 분포를 생성한다.
RealType m() const
m
을 돌려준다.
RealType stddev() const
s
를 돌려준다.
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
normal_distribution<RealType = double>
는 주로 복잡한 현상을 기술하는 과학에 사용된다. 변량을 예측하거나 측정할 때 에러는 정규적으로 분포되어 있다고 간주된다.
두 개의 모수 평균과 표준 편차가 있다.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType mean = RealType(0), RealType stddev = RealType(1)); RealType mean() const; RealType stddev() const; };
생성자와 멤버:
normal_distribution<>(RealType mean = 0, RealType stddev = 1)
mean
과 stddev
로 정규 분포를 생성한다. 기본 모수 값은 표준 정규 분포를 정의한다.
normal_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 정규 분포를 생성한다.
RealType mean() const
mean
모수를 돌려준다.
RealType stddev() const
stddev
모수를 돌려준다.
result_type min() const
result_type
유형의 가장 낮은 양의 값을 돌려준다.
result_type max() const
result_type
유형의 최대 값을 돌려준다.
negative_binomial_distribution<IntType = int>
확률 분포는 지정된 실패 횟수가 일어나기 전에 일련의 베르누이 시행에서 성공한 횟수를 기술한다. 예를 들어 세 번째에 1이 나올 때까지 주사위를 반복적으로 던지면 다른 면이 나올 확률 분포는 음의 이항 분포이다.
모수가 두 개이다. (IntType
) k (> 0)는 실험을 멈출 때까지 실패한 횟수이고 (double
) p는 각 시행마다 성공할 확률이다.
정의된 유형:
typedef IntType result_type; struct param_type { explicit param_type(IntType k = IntType(1), double p = 0.5); IntType k() const; double p() const; };
생성자와 멤버:
negative_binomial_distribution<>(IntType k = IntType(1),
double p = 0.5)
k
와 p
로 negative_binomial 분포를 생성한다.
negative_binomial_distribution<>(param_type const ¶m)
param
에 저장된 값들에 맞게 negative_binomial 분포를 생성한다.
IntType k() const
k
를 돌려준다.
double p() const
p
를 돌려준다.
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
poisson_distribution<IntType = int>
는 고정된 시간 사이에 일어나는 사건들의 확률을 모델링한다. 이 사건들은 알려진 확률로 일어나고 이전 사건 이후로 시간에 독립적으로 일어난다.
모수는 mean
한 개이다. 일정 시간에 예상되는 사건의 갯수를 지정한다. 예를 들어 일분 동안 평균 2 개의 사건이 발생하고 연구 대상이 10분이면 mean = 20
이다.
정의된 유형:
typedef IntType result_type; struct param_type { explicit param_type(double mean = 1.0); double mean() const; };
생성자와 멤버:
poisson_distribution<>(double mean = 1)
mean
모수로 포아송 분포를 생성한다.
poisson_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 포아송 분포를 생성한다.
double mean() const
mean
모수를 돌려준다.
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
student_t_distribution<RealType = double>
는 작은 샘플 크기로부터 정규 분포의 모집단의 평균을 예측할 때 사용되는 확률 분포이다.
모수가 한 개인 특징이 있다. 자유도(degrees of freedom)가 그것으로서, 샘플 크기 - 1과 같다.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType n = RealType(1)); RealType n() const; // 자유도 };
생성자와 멤버:
student_t_distribution<>(RealType n = RealType(1))
student_t_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 student_t 분포를 생성한다.
RealType n() const
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
uniform_int_distribution<IntType = int>
균등하게 분포된 정수 값의 범위에서 무작위로 정수 값을 선택한다.
모수가 a
와 b
두 개이고 각각 반환 가능한 최소값과 최대값을 지정한다.
정의된 유형:
typedef IntType result_type; struct param_type { explicit param_type(IntType a = 0, IntType b = max(IntType)); IntType a() const; IntType b() const; };
생성자와 멤버:
uniform_int_distribution<>(IntType a = 0, IntType b = max(IntType))
uniform_int_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 uniform_int 분포를 생성한다.
IntType a() const
a
를 돌려준다.
IntType b() const
b
를 돌려준다.
result_type min() const
result_type max() const
uniform_real_distribution<RealType = double>
는 균등하게 분포된 RealType
값의 범위로부터 무작위로 RealType
값을 선택할 수 있다.
모수가 a
와 b
두 개이고, 각각 돌려줄 수 있는 값의 범위를 지정한다 ([a, b)
).
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType a = 0, RealType b = max(RealType)); RealType a() const; RealType b() const; };
생성자와 멤버:
uniform_real_distribution<>(RealType a = 0, RealType b = max(RealType))
uniform_real_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 uniform_real 분포를 생성한다.
RealType a() const
a
를 돌려준다.
RealType b() const
b
를 돌려준다.
result_type min() const
a
의 최소 값을 돌려준다.
result_type max() const
b
의 최대 값을 돌려준다.
weibull_distribution<RealType = double>
는 주로 신뢰 공학과 수명 분석에 사용된다.
두세 개의 매개변수가 있고 STL은 두 개짜리 매개변수 변형을 제공한다. 세 개짜리 매개변수 변형은 모양 (즉, 기울기) 매개변수와 축척 매개변수 그리고 위치 매개변수가 있다. 두 개의 모수 변량은 묵시적으로 모수 값 0의 위치를 사용한다. 두 개짜리 매개변수 변형에는 a 모양 매개변수와 b 축척 매개변수가 제공된다. 베이불의 모수의 의미를 다룬 흥미로운 글은 다음을 참고하라
http://www.weibull.com/hotwire/issue14/relbasics14.htm.
정의된 유형:
typedef RealType result_type; struct param_type { explicit param_type(RealType a = RealType(1), RealType b = RealType(1)); RealType a() const; // 모양 (기울기) 매개변수 RealType b() const; // 축척 매개변수 };
생성자와 멤버:
weibull_distribution<>(RealType a = 1, RealType b = 1)
a
와 b
로 베이불 분포를 생성한다.
weibull_distribution<>(param_type const ¶m)
param
구조체에 저장된 값들에 맞게 베이불 분포를 생성한다.
RealType a() const
RealType stddev() const
result_type min() const
result_type max() const
result_type
유형의 최대 값을 돌려준다.
truncate
(2), opendir(2), 그리고 realpath(3) 같은) 그런 함수들을 C++에서도 사용할 수 있지만, 서명과 사용법이 별로 마음에 안드는 경우가 많다. 매개변수로 char const *
를 요구하고 malloc(3)과 free(3)에 기반하여 정적 버퍼나 메모리 할당을 사용하기 때문이다.
이 함수들을 둘러싼 포장함수를 2003년 이후로 부스트 라이브러리에서 얻을 수 있었다. 현재 C++는 직접적으로 이 편의기능들을 std::(experimental::)filesystem
이름공간에 제공한다. 이 편의기능을 사용하려면 이 헤더를 먼저 포함해야 한다. 그리고 std::(experimental::)filesystem
이름공간으로부터 이 편의기능을 이용하는 프로그램은 stdc++fs
라이브러리를 링크해야 한다: -lstdc++fs
.
filesystem
이름공간은 현재 experimental
이름공간 아래에 내포되어 있지만, 모든 기능을 사용할 수 있다. 때가 되면 filesystem
앞의 `실험적'이라는 꼬리표는 떼어내게 될 것이다.
filesystem
이름공간은 광범위하다. 10개 이상의 클래스를 제공하고 30개가 넘는 자유 함수를 지원한다.
이 절과 그 아래의 항에서 fs::
표기를 사용하여 std::(experimental::)filesystem
이름공간을 가리키겠다.
std::(experimental::)filesystem
이름공간은 따로 filesystem_error
예외 유형을 제공한다. 생성자는 서명이 다음과 같다 (역따옴표가 붙은 매개변수는 선택적이다):
filesystem_error(string const &what, [path const &path1, [path const &path2,]] error_code ec);
filesystem
편의기능은 표준 시스템 기능과 밀접하게 관련되어 있다. errc
에러 코드 열거체 값에서 error_code
를 얻어 filesystem_error
에 건넨다. 다음의 작은 프로그램으로 이를 보여준다.
#include <iostream> #include <experimental/filesystem> namespace fs = std::experimental::filesystem; using namespace std; int main() try { try { throw fs::filesystem_error{ "exception encountered", "p1", "p2", make_error_code(errc::address_in_use) }; } catch (fs::filesystem_error const &fse) { cerr << fse.what() << ",\n" << fse.path1() << ",\n" << fse.path2() << ",\n" << fse.code() << '\n'; throw; } } catch (exception const &ec) { cerr << ec.what() << '\n'; }
fs::path
클래스의 객체는 파일 시스템 항목의 이름을 담고 있다. path
클래스는 값-유형의 클래스이다. 기본 생성자(빈 경로)와 더불어 표준 복사/이동 생성/할당 기능을 사용할 수 있다. 그리고 다음 생성자를 사용할 수 있다.
path(string &&tmp)
path(Type const &source)
path(InputIter begin, InputIter end)
이 생성자들은 (NTBS를 포함하여) 다양한 형태의 문자 연속열을 인자로 기대한다. 개념적으로 이 연속열은 다음 요소로 구성된다 (모두 선택적임).
E:
처럼) 디스크 이름이나 (//nfs
같은) 장치 표시자.
.
)짜리 파일이름'은 현재 디렉토리를 나타내고 `점 두 개(..
)짜리 파일이름'은 현재 디렉토리의 부모 디렉토리를 가리킨다.
생성자의 마지막 매개변수는 format ftmp = auto_format
인데, 비-기본 규격을 거의 요구하지 않을 것이다. (이 매개변수는 cppreference를 참고하라.)
변경을 담당하는 멤버 함수는 다음과 같다.
path &append(Type const &arg)
또는 path &operator/=(Type const &arg)
:
(반복자를 포함하여) 생성자에 건넬 수 있는 인자들은 이 멤버에도 건넬 수 있다. (소스 경로가 비어 있고 인자가 절대 경로를 나타내지 않을 경우에 결과가 절대 경로가 아닌 한) 인자를 추가하기 전에 현재 내용과 arg
의 내용을 디렉토리 구분자로 가른다.
void clear()
: path
의 내용을 삭제한다.
int compare(Type const &other)
: 현재 경로의 내용을 other
의 내용과 사전 순서로 비교한 결과를 돌려준다. other
는 경로나 문자열 유형 또는 NTBS이다.
path &concat(Type const &arg)
또는
path &operator+=(Type const &arg)
:
append
와 비슷하지만 arg
의 내용을 현재 path
의 내용에 추가할 때 디렉토리 가름자를 사용하지 않는다.
path &remove_filename()
:
저장된 경로에서 마지막 구성요소를 제거한다. 루트 디렉토리만 저장되어 있으면 그 루트 디렉토리가 삭제된다. 경로를 구성하는 유일한 요소가 아닌 한, 마지막 디렉토리 가름자는 유지됨을 눈여겨보라.
path &replace_extension(path const &replacement = path{} )
:
저장된 경로의 마지막 구성요소의 확장자(점까지 포함)를 replacement
로 교체한다. replacement
가 비어 있으면 확장자는 제거된다. replace_extension
를 호출한 path
에 확장자가 없으면 replacement
가 추가된다.
path &replace_filename(path const &replacement)
:
저장된 경로의 마지막 구성요소를 replacement
로 교체한다. 교체 경로 자체가 여러 요소로 구성되어 있을 수 있다. 루트 디렉토리만 저장되어 있으면, replacement
로 교체된다.replace_filename
을 호출하기 전에 path.empty()
가 true
를 돌려주는 경우에 멤버의 행위는 정의되어 있지 않다.
(인자도 없고, 불변 멤버인) 접근자는 경로의 내용을 NTBS(c_str
)로 돌려준다. 또는 문자열 (string, wstring, u8string, u16string, u32string
) (generic_string
처럼 앞에 gneric_
접두사가 붙어 있을 수 있다) 로 돌려주거나 아니면 path
로 돌려받은 경로 규격의 구성요소로 돌려준다. 예제:
fs::path path{ "/usr/local/bin" }; cout << path.string() << '\n'; // 출력: /usr/local/bin
경로(path
) 객체는 <<
(스트림 삽입) 연산자를 사용하여 스트림에 삽입된다. 겹따옴표로 경로 이름을 둘러싸 화면에 보여준다. 경로의 내용에 NTBS나 문자열로 접근하면 또는 그것을 문자열에 (형변환하거나) 할당하면 겹따옴표는 제거된다.
경로(path
) 객체는 또 >>
(스트림 추출) 연산자를 사용하여 스트림으로부터 추출된다. 경로는 선택적으로 겹따옴표로 둘러싸일 수도 있다. 추출된 경로를 또 다시 따옴표가 둘러싼다.
begin
반복자와 end
반복자를 사용하여 path
의 모든 구성요소를 반복할 수 있다. 각 구성요소가 path
로 반환된다. 맨 처음 루트 이름과 루트 디렉토리가 구성요소로 반환된다. 다음에 개별 디렉토리 (그리고 마지막 파일이름) 구성요소가 따라온다. path
의 구성요소를 방문할 때 디렉토리 가름자 자체는 반환되지 않는다.
다음은 객체를 분해하여 돌려주는 구성요소들이다 (요구한 구성요소가 없으면 비어 있다): root_name, root_directory, root_path, relative_path, parent_path
(즉, 마지막 요소가 제거된 현재 내용), filename, stem
(즉, 점-확장자가 없는 파일이름), 그리고 extension
(확장자). 예제:
fs::path path{ "/usr/local/bin" }; cout << path.relative_path() << '\n'; // 출력: "usr/local/bin" // (겹따옴표에 주목)
앞에 접두사로 has_
를 붙이면 멤버는 bool
값을 돌려준다. 구성요소가 존재하면 이 값은 true
이다. 다음 멤버도 사용할 수 있다: is_absolute, is_relative
멤버 함수외에도 자유 함수를 사용할 수 있다: ==, !=, <, <=, >,
그리고 >=
는 두 개의 path
객체를 비교한다. /
는 lhs
와 rhs
를 결합하여 돌려준다. (마치 string
멤버가 돌려주는 반환 값을 비교하는 것처럼) 사전 순서로 비교한다.
(기존의 디렉토리를 참조하는) path
를 (경로에 .이나 ..이 담겨 있지 않은) 표준 형태로 변환하려면 canonical
자유 함수를 사용할 수 있다 (18.9.7항).
fs::path path{ "/usr/local/bin/../../share/man" }; cout << canonical(path) << '\n'; // 출력: "/usr/share/man"
directory_entry
클래스의 객체에 디렉토리 엔트리의 이름과 상태가 담긴다. 모든 표준 생성자와 할당 연산자 외에도 path
를 기대하는 생성자가 정의되어 있다:
directory_entry(path const &entry);
entry
가 반드시 존재할 필요는 없다.
멤버 함수는 다음과 같다.
void assign(path const &dest)
:현재 경로를 dest
로 교체한다.
void replace_filename(path const &dest)
:현재 경로 이름의 마지막 요소를 (현재 이름이 디렉토리 가름자에서 끝나면 비어 있을 수 있다) dest
로 교체한다.
path const &path() const
,
operator path const &() const
:
현재 경로 이름을 돌려준다.
file_status status([error_code &ec])
:현재 경로 이름의 유형과 속성을 돌려준다. 현재 경로 이름이 심링크를 참조하고, 심링크의 유형과 상태가 요구되면 symlink_status
를 사용하라 (18.9.5항).
또, directory_entry
객체는 ==, !=, <, <=, >,
그리고 >=
연산자를 사용하여 비교할 수 있다. 연산자를 path
객체에 적용한 결과를 돌려준다.
filesystem
이름공간에 디렉토리를 간단하게 처리해 줄 두 개의 클래스가 있다. directory_iterator
클래스의 객체는 디렉토리 엔트리를 반복하는 (입력) 반복자이다. recursive_directory_iterator
클래스의 객체는 디렉토리의 모든 엔트리를 재귀적으로 반복하는 (입력) 반복자이다.
(recursive_)directory_iterator
클래스는 기본 생성자와 복사 생성자 그리고 이동 생성자를 지원한다. 두 클래스의 객체는 path
와 선택적인 error_code
로 구성된다. 예를 들어,
directory_iterator(path const &dest [, error_code &ec]);이 클래스는 표준 입력 반복자의 모든 멤버를 지원한다 (18.2절). 현재 저장된 경로가 역참조 연산자로 반환된다.
cout << *fs::directory_iterator{ "/home" } << '\n'; // /home 아래의 // 첫 엔트리를 보여준다.
이 객체에 부합하는 끝 반복자는 클래스의 기본 생성 객체이다. 그리고 filesystem::begin
와 filesystem::end
를 사용할 수 있다. 범위-기반의 for 회돌이에 자동으로 사용된다. 예를 들어 다음 서술문으로 /var/log
디렉토리의 모든 엔트리가 (겹따옴표로 둘러싸여) 화면에 보여진다.
for (auto &entry: fs:directory_iterator("/var/log")) cout << entry << '\n';대안으로, 명시적으로 반복자를 정의한 for-서술문도 사용할 수 있다. 예를 들어,
for ( auto iter = fs:directory_iterator("/var/log"), end = fs::directory_iterator{}; iter != end; ++iter ) cout << entry << '\n';
fs::(recursive_)directory_iterator base{"/var/log"}
객체는 디렉토리의 첫 요소를 나타낸다. auto &iter = begin(base), auto iter = begin(base), auto &iter = base
또는 auto iter = base
처럼 명시적으로 정의된 반복자를 사용할 수 있다. 모두 base
의 데이터를 참조한다. 이 반복자를 증가시키면 데이터도 base
를 다음 요소로 이동시킨다:
fs::recursive_directory_iterator base{ "/var/log/" }; auto iter = base; // 뒤의 두 요소는 경로가 동일하다. // 첫 원소는 경로가 다르다. cout << *iter << ' ' << *++iter << ' ' << *base << '\n';
recursive_directory_iterator
는 directory_options
인자도 받는다 (아래 참고). 기본값은 directory_options::none
이다.
recursive_directory_iterator(path const &dest, directory_options options [, error_code &ec]);
enum class directory_options
는 recursive_directory_iterator
객체의 행위를 세밀하게 조율할 수 있는 값들을 정의한다. 비트별 연산자를 지원한다 (심볼 값은 괄호 사이에 보여준다). 다음은 모든 심볼을 개관한 것이다:
none
(0): 디렉토리 심링크는 건너뛴다. 하위 디렉토리에 진입이 불가하면 에러가 일어난다.
follow_directory_symlink
(1): 하위 디렉토리를 참조하는 심링크가 따라온다.
skip_permission_denied
(2): 진입 불가 디렉토리라면 조용히 건너뛴다.
directory_iterator
클래스의 멤버와 함께 recursive_directory_iterator
클래스는 다음 멤버들을 더 제공한다:
int depth() const
:현재 반복의 깊이를 돌려준다. (생성 시간에 지정된) 최초 디렉토리에서 깊이(depth
)는 0을 돌려준다.
void disable_recursion_pending()
:
반복자의 증가 연산자 또는 멤버를 호출하기 전에 호출될 때 하위 디렉토리이면 다음 엔트리는 반복하지 않는다. 증가 연산를 실행한 후 곧바로 다시 재귀를 허용한다. 그래서 재귀가 특정한 깊이에서 끝나야 한다면 depth()
가 해당 깊이를 돌려줄 때까지 이 함수를 반복적으로 호출해야 한다.
recursive_directory_iterator &increment(error_code &ec)
:
반복자의operator++()
와 똑같이 행위한다. 그렇지만 에러가 일어나면operator++
는filesystem_error
를 던지는 반면에increment
함수는 적절한 에러를ec
에 할당한다.
directory_options options() const
:
생성 시간에 지정된 옵션들을 돌려준다.
void pop()
:
현재 디렉토리의 처리를 끝낸다. 현재 디렉토리의 부모에서 다음 엔트리를 계속 처리한다. 최초 디렉토리에서 호출되면 디렉토리 처리를 끝낸다.
bool recursion_pending() const
:
반복자가 현재 가리키는 엔트리가 디렉토리이면 true
를 돌려준다. 그 안으로 반복자가 증가하면 디렉토리 처리가 계속될 것이다.
마지막으로, 다음 데모는 현재 디렉토리와 그 하위 디렉토리의 모든 엔트리를 보여준다.
int main() { fs::recursive_directory_iterator base{ "/var/log" }; for (auto entry = base, end = fs::end(base); entry != end; ++entry) { cout << entry.depth() << ": " << *entry << '\n'; if (entry.depth() == 1) entry.disable_recursion_pending(); } }
file_status
클래스 객체에 파일 시스템 엔트리의 유형과 접근권한이 담긴다. 복사 생성자와 이동 생성자 그리고 할당 연산자를 사용할 수 있다. 그리고 다음 생성자를 정의한다.
explicit file_status(file_type type = file_type::none, perms permissions = perms::unknown)이 생성자는 기본 생성자로 사용할 수도 있다. 그리고 다음 멤버를 정의한다.
perms permissions() const
그리고 void permissions(perms newPerms)
:
앞의 멤버는 현재 접근 권한 집합을 돌려주고 뒤의 멤버는 그 집합을 변경할 수 있다.
file_type type() const
and void type(file_type type)
:
앞의 멤버는 현재 유형을 돌려주고 뒤의 멤버는 그 유형을 변경할 수 있다.
enum class file_type
은 다음 심볼을 정의한다:
not_found = -1
: 파일이 발견되지 않음 (에러로 간주되지 않는다);
none
: 파일 상태를 아직 평가하지 못했다. 아니면 평가할 때 에러가 일어났다.
regular
: 표준 파일;
directory
: 디렉토리;
symlink
: 심볼 링크;
block
: 블록 장치;
character
: 문자 장치;
fifo
: 명명 파이프;
socket
: 소켓 파일;
unknown
: 미지의 파일 유형.
enum class perms
는 파일 시스템 엔트리의 모든 접근 권한을 정의한다. 열거체의 심볼은 <sys/stat.h>
헤더 파일에 정의된 상수들보다 의미를 더 잘 기술하도록 선택되었다. 그러나 값은 똑 같다. 비트별 연산자도 모두 enum class perms
의 값으로 사용할 수 있다. 다음은 정의된 심볼을 모두 개관한다.
심볼 값 sys/stat.h 의미 none 0000 접근 권한 비트가 설정되어 있지 않음 owner_read 0400 S_IRUSR 파일 소유자가 읽기 권한이 있음 owner_write 0200 S_IWUSR 파일 소유자가 쓰기 권한이 있음 owner_exec 0100 S_IXUSR 파일 소유자가 실행/검색 권한이 있음 owner_all 0700 S_IRWXU 파일 소유자가 읽기, 쓰기, 실행/검색 권한이 있음 group_read 0040 S_IRGRP 파일 그룹이 읽기 권한이 있음 group_write 0020 S_IWGRP 파일 그룹이 쓰기 권한이 있음 group_exec 0010 S_IXGRP 파일 그룹이 실행/검색 권한이 있음 group_all 0070 S_IRWXG 파일 그룹이 읽기, 쓰기, 실행/검색 권한이 있음 others_read 0004 S_IROTH 기타 사용자가 읽기 권한이 있음 others_write 0002 S_IWOTH 기타 사용자가 쓰기 권한이 있음 others_exec 0001 S_IXOTH 기타 사용자가 실행/검색 권한이 있음 others_all 0007 S_IRWXO 기타 사용자가 읽기, 쓰기, 실행/검색 권한이 있음 all 0777 모든 사용자가 읽기, 쓰기, 실행/검색 권한이 있음 set_uid 04000 S_ISUID 실행시에 사용자 ID를 파일 소유자의 사용자 ID에 부여함 set_gid 02000 S_ISGID 실행시에 그룹 ID를 파일 그룹의 사용자 ID에 부여함 sticky_bit 01000 S_ISVTX POSIX XSI에 의하면 디렉토리에 설정할 때 그 디렉토리에 기타 사용자가 (/tmp를 사용하여) 쓸 수 있을지라도 파일 소유자만 파일을 삭제할 수 있다. mask 07777 권한 비트를 모조리 활성화
다음 작은 프로그램은 파일 상태를 어떻게 결정하고 사용할 수 있는지 보여준다.
namespace { std::unordered_map<fs::file_type, char const *> map = { { fs::file_type::not_found, "an unknown file" }, { fs::file_type::none, "not yet or erroneously evaluated " "file type" }, { fs::file_type::regular, "a regular file" }, { fs::file_type::directory, "a directory" }, { fs::file_type::symlink, "a symbolic link" }, { fs::file_type::block, "a block device" }, { fs::file_type::character, "a character device" }, { fs::file_type::fifo, "a named pipe" }, { fs::file_type::socket, "a socket file" }, { fs::file_type::unknown, "an unknown file type" } }; void status(fs::path const &path) { fs::file_status stat = fs::directory_entry{ path }.symlink_status(); cout << path << " is " << map[stat.type()] << '\n'; }; } // anon. namespace int main() { for (auto entry: fs::directory_iterator{"/home/frank"}) ::status(entry); cout << "File status_known is " << status_known( fs::file_status{} ) << '\n'; }
path
객체는 특정한 파일 시스템 안에 산다. 파일 시스템은 일정량의 데이터를 담을 수 있다 (바이트의 갯수로 표현됨). 그 중의 일부는 이미 사용중이고 또 일부는 여전히 사용이 가능하다. 이 세가지 정보는 fs::space
함수로 만들 수 있다. fs::path const &
를 기대하고, POD struct fs::space_info
에 있는 정보를 돌려준다. 이 함수는 filesystem_error
를 던진다. path
를 첫 인자로 받고 운영 체제의 에러 코드를 error_code
인자로 받는다. 중복정의 space
함수는 error_code
객체를 두 번째 인자로 기대한다. 이 인자는 에러가 일어나지 않으면 소거되고 에러가 일어나면 운영 체제의 에러 코드가 설정된다.
반환된 fs::space_info
는 세 개의 필드가 있다:
uintmax_t capacity; // 바이트 단위로 총 크기 uintmax_t free; // 파일 시스템의 여유 바이트 갯수 uintmax_t available; // 프로세스가 점유하지 않은 여유 바이트 갯수필드를 결정할 수 없으면 -1로 설정된다 (즉,
uintmax_t
유형의 최대값이다).
다음 프로그램은 space
를 어떻게 사용하는지 보여준다.
int main() { fs::path p{ "/tmp" }; auto pod = fs::space(p); std::cout << "The filesystem containing /tmp has a capacity of " << pod.capacity << " bytes,\n" "i.e., " << pod.capacity / 1024 << " KB.\n" "# free bytes: " << pod.free << "\n" "# available: " << pod.available << '\n'; }
선택적인 path const &base
매개변수가 기본으로 정의된 함수는 current_path
를 사용한다.
그 중에는 error_code &ec
매개변수를 정의한 함수도 있다. 이 함수는 noexcept
규격이 있다. 이 함수가 임무를 완수하지 못하면 ec
에 적절한 에러 코드가 설정된다. 에러가 일어나지 않으면 ec.clear()
가 호출된다. ec
인자를 제공하지 않으면 이 함수는 임무를 완수하지 못할 경우 filesystem_error
를 던진다.
다음 함수를 사용할 수 있다:
path absolute(path const &src, path const& base)
:src
의 사본이다.src
에 이미 있지 않는 한,absolute(base)
의 루트 이름과 루트 디렉토리가 앞에 붙는다.
path canonical(path const &src [, path const &base [, error_code &ec]])
:
src
의 표준 경로를 돌려준다 (src
가 절대 경로가 아니면 앞에base
가 붙는다.).
void copy(path const &src, path const &dest [, copy_options opts [, error_code &ec]])
:
src
가 반드시 존재해야 한다.cp
프로그램도 성공한다면src
를dest
에 복사한다. 복사 옵션을 지정해 행위를 세밀하게 조정할 수 있다. 아래에서 옵션에 지정할 수 있는 값들을 참고하라.
src
가 디렉토리이고dest
가 존재하지 않으면dest
가 생성된다. 복사 옵션으로recursive
나none
이 지정되었다면 디렉토리는 재귀적으로 복사된다.
bool copy_file(path const &src, path const &dest [,
copy_options opts [, error_code &ec]])
:src
가 반드시 존재해야 한다.cp
프로그램도 성공한다면src
를dest
에 복사한다. 다음에 심볼릭 링크가 따라온다.skip_existing
복사 옵션을 사용하면dest
가 이미 존재할 경우src
는 복사되지 않는다.overwrite_existing
복사 옵션을 사용하면 강제로 복사한다.update_existing
복사 옵션이면src
가dest
보다 더 최신일 경우 복사된다. 복사에 성공하면true
가 반환된다;
void copy_symlink(path const &src, path const &dest [, error_code &ec])
:
src
심링크의 사본으로dest
심링크를 만든다.
bool create_directories(path const &dest [,
error_code &ec])
:
이미 존재하지 않는 한,dest
의 각 구성요소를 만든다. 에러가 일어나지 않으면true
를 돌려준다. 아래create_directory
도 참고하라.
bool create_directory(path const &dest [, path
const &existing] [, error_code &ec])
:
아직 존재하지 않으면 디렉토리dest
를 만든다. 디렉토리dest
가 이미 있더라도 에러가 아니다.dest
의 부모 디렉토리가 반드시 존재해야 한다.existing
이 지정되면dest
는existing
과 같은 속성을 받는다. 에러가 일어나지 않으면true
를 돌려준다.
bool create_directory_symlink(path const &dir, path
const &link [, error_code &ec])
:
create_symlink
와 비슷하지만 디렉토리를 참조하는 심링크를 만든다. 아래에서create_symlink
도 참고하라.
bool create_hardlink(path const &dest, path const
&link [, error_code &ec])
:
link
로부터dest
를 가리키는 하드링크를 만든다.dest
가 반드시 존재해야 한다.
bool create_symlink(path const &dest, path const
&link [, error_code &ec])
:
dest
를 가리키는 심볼릭 (소프트) 링크를link
로부터 만든다.dest
는 존재하지 않아도 된다.
path current path([error_code &ec])
, void current_path(path const &toPath [, error_code &ec])
:
앞의 함수는 현재 작업 디렉토리(cwd)를 돌려준다. 뒤의 함수는 cwd를 toPath
로 바꾼다.
bool equivalent(path const &path1, path const &path2 [, error_code &ec])
:
path1
과path2
가 같은 파일이나 디렉토리를 참조하고 상태가 동일하면true
를 돌려준다. 두 경로 모두 존재해야 한다.
bool exists(path const &dest [, error_code &ec])
, exists(file_status status)
:
dest
가 존재하면 (실제로는status(dest[, ec])
가true
를 돌려주면 (아래 참고))true
를 돌려준다. 디렉토리를 반복할 때 반복자는 일반적으로 엔트리의 상태를 제공한다는 것을 눈여겨보라. 그런 경우에exists(iterator->status())
를 호출하는 편이exists(*iterator)
를 호출하는 것보다 더 효율적이다.
std::unintmax_t file_size(path const &dest [, error_code &ec])
:
표준 파일의 (또는 심링크 목적지의) 바이트 갯수를 돌려준다.
std::uintmax_t hard_link_count(path const &dest [, error_code &ec])
:
dest
에 연관된 하드 링크의 갯수를 돌려준다.
file_time_type last_write_time(path const &dest [,
error_code &ec])
, void last_write_time(path const &dest,
file_time_type newTime [, error_code &ec])
:
앞의 함수는dest
의 마지막 변경 시간을 돌려준다. 뒤의 함수는dest
의 마지막 변경 시간을newTime
으로 수정한다.file_time_type
반환 유형은chrono::time_point
에 대하여using
별칭을 사용하여 정의된다 (20.1.4항). 반환된time_point
는 현재 파일 시스템에서 만나는 파일의 시간을 모두 확실하게 다룰 수 있다.
void permissions(path const &dest, perms spec [,
error_code &ec])
:
dest
의 접근 권한을spec
으로 설정한다.perms::add_perms
이나perms::remove_perms
이 설정되어 있는 경우는 제외한다.perms
에 있는 접근 권한은perms::mask
를 사용하여 가릴 수 있다.
path read_symlink(path const &src [, error_code &ec])
:
src
는 반드시 심볼 링크를 참조해야 한다. 그렇지 않으면 에러가 일어난다. 링크의 대상을 돌려준다.
bool remove(path const &dest [, error_code &ec])
, std::uintmax_t remove_all(path const &dest [, error_code &ec])
:
remove
함수는 파일이나 심링크 또는 빈 디렉토리dest
를 제거한다.dest
를 제거할 수 있으면true
를 돌려준다.remove_all
함수는dest
가 파일이거나 심링크이면 제거한다.dest
가 디렉토리이면 재귀적으로 제거하고 삭제된 엔트리의 갯수를 돌려준다.
void rename(path const &src, path const &dest [, error_code &ec])
:
표준 mv(1) 명령어를 사용한 것처럼src
를dest
로 이름을 바꾼다.
void resize_file(path const &src, std::uintmax_t size [,
error_code &ec])
:
마치 표준 truncate(1) 명령어를 사용한 것처럼src
의 크기를size
로 바꾼다.
space_info space(path const &src [, error_code &ec])
:
src
가 위치한 파일 시스템에 관한 정보를 돌려준다.
file_status status(path const &dest [, error_code &ec])
:
dest
의 유형과 속성을 돌려준다. 심볼릭 링크의 유형과 속성을 요구하면symlink_status
를 사용하라.
bool status_known(file_status status)
:
결정된 상태를status
가 참조하면true
를 돌려준다 (status
가 참조하는 엔트리가 존재하지 않는다는 뜻일 수 있다).false
를 받는 한 가지 방법은 존재하지 않는 엔트리를 기본file_status: status_known(file_status{})
생성자에 건네는 것이다.
path system_complete(path const &src[, error_code&
ec])
:
src
에 부합하는 절대 경로를 돌려준다.current_path
를 바닥 경로로 사용한다;
path temp_directory_path([error_code& ec])
:
임시 파일을 위해 사용할 수 있는 디렉토리 경로를 돌려준다. 디렉토리가 생성되지는 않지만 그 이름은 일반적으로 환경 변수TMPDIR
이나TMP
또는TEMP
나TEMPDIR
에서 얻을 수 있다. 그렇지 않으면/tmp
가 반환된다.
파일 유형은 다음 함수로 질의할 수 있다. 모두 다음 서명을 지원한다 (WHATEVER
는 요청된 규격이다):
bool is_WHATEVER(file_status status) bool is_WHATEVER(path const path &dest [, error_code &ec])모든 함수는 요청된 유형에
dest
이나 status
가 부합하면 true
를 돌려준다. 다음은 그 함수들이다:
is_block_file
: 경로가 블록 장치를 참조한다;
is_character_file
: 경로가 문자 장치를 참조한다;
is_directory
: 경로가 디렉토리를 참조한다;
is_empty
: 경로가 빈 파일이나 디렉토리를 참조한다;
is_fifo
: 경로가 명명(이름붙은) 파이프를 참조한다;
is_other
: 경로가 디렉토리나 표준 파일 또는 심링크를 참조하지 않는다;
is_regular_file
: 경로가 표준 파일을 참조한다;
is_socket
: 경로가 명명 소켓을 참조한다;
is_symlink
: 경로가 심볼릭 링크를 참조한다;
enum class copy_options
은 copy
함수와 copy_file
함수의 행위를 세밀하게 조율할 수 있는 심볼 상수를 정의한다. 이 열거체는 비트별 연산자를 지원한다 (심볼의 값을 괄호 사이에 보여준다). 다음은 정의된 모든 심볼을 개관한다.
파일을 복사할 때의 옵션:
none
(0): 에러를 보고함 (기본 행위);
skip_existing
(1): 기존의 파일 유지. 에러를 보고하지 않음;
overwrite_existing
(2): 기존의 파일 교체;
update_existing
(4): 현재 복사중인 파일보다 오래된 기존의 파일만 교체;
하위 디렉토리를 복사할 때의 옵션:
none
(0): 하위 디렉토리 무시 (기본 행위);
recursive
(8): 하위 디렉토리와 그 내용을 재귀적으로 복사;
심링크를 복사할 때의 옵션:
none
(0): 심링크를 따라감 (기본 행위);
copy_symlinks
(16): 심링크는 심링크로 복사. 가리키는 실제 파일을 복사하지 않는다;
skip_symlinks
(32): 심링크 무시;
copy
행위를 제어하는 옵션:
none
(0): 파일 내용을 복사 (기본 행위);
directories_only
(64): 디렉토리 구조를 복사하지만 파일은 복사하지 않음;
create_symlinks
(128): 파일 사본을 만들지 않고 원래 파일을 가리키는 심링크를 만든다 (목적 경로가 현재 디렉토리에 있지 않는 한, 소스 경로는 반드시 절대 경로이어야 한다.).
create_hard_links
(256): 파일 사본을 만들지 않고 원래 파일과 같은 파일로 결정되는 하드링크를 만든다.