2장 - C++ std::string 클래스

1) string 클래스란

앞서 소개한 cstring의 함수와 기능은 비슷하지만, 메모리 할당 작업을 처리해주는 기능이 추가된 클래스이다. std 네임스페이스에 속한다. C 스타일 스트링은 뭐가 문제였을까?

  • 장점
    • 간단함 - 내부적으로 기본 문자 타입과 배열 구조체로 처리
    • 가벼움 - 메모리 최소 사용
    • 로우레벨 - 메모리에 대한 세부 조작이 용이
  • 단점
    • 고차원 기능을 구현하려면 시간이 필요
    • 찾기 힘든 메모리 버그 발생 가능
    • 객체지향적 특성 반영 미비
    • 내부 표현 방식을 이해하고 있어야함

그렇게 장점은 유지하고 단점은 보완한 C++의 std::string이 탄생했다.


2) string 클래스 사용법

  • 실제로는 클래스지만 기본 타입처럼 사용한다.
  • +기호를 사용해 스트링을 덧붙이는 연산이 가능하다.
  • 스트링의 각 문자는 []를 사용해서 접근 가능하다.
  • ==, !=같은 연산자를 오버로딩해서 적용할 수 있다.

**<고급>**

  • 스트링 할당이나 크기 조절 코드가 흩어져있어도 메모리 누수가 발생하지 않는다.

    → 스트링 객체는 모두 스택 변수로 생성되기 때문이다. 스트링 객체가 스코프를 벗어난다면 string 소멸자가 정리한다.

  • C언어에 대한 호환성을 제공한다

    → 스트링 클래스의 c_str()을 사용해서 C와의 호환성을 보장한다. 해당 함수는 C스타일 스트링을 표현하는 const char*을 리턴한다. 다만 메모리 이동이나 객체 제거시에는 더이상 포인터를 사용할 수 없음에 유의하자.


3) string literal

C에서의 스트링 리터럴은 const char*였다. 표준 사용자 정의 리터럴을 사용하여 타입을 std::string으로 변경할 수 있다(std::string_literals에 존재).

auto str1 = "Hello World";
auto str2 = "Hello World"s;

cout << typeid(str1).name() << endl;
cout << typeid(str2).name() << endl;

Untitled


4) 숫자와 스트링 사이의 변환

숫자 → 스트링

std네임스페이스는 여러 종류의 to_string()함수를 제공한다. 이들은 새로운 string 객체를 생성하여 리턴한다.

string to_string(int val);
string to_string(long val);
...

스트링 → 숫자

숫자→스트링 함수가 모두 이름이 같고 매개변수가 달랐다면, 스트링→숫자는 동일한 매개변수에 다른 이름과 반환 타입을 가진다. 이름의 구성은 sto+ 바꾸려는 타입의 첫 글자이다.

int stoi(const string& str, size_t* idx=0, int base = 10);
long stol(const string& str, size_t* idx=0, int base = 10);
long long stoll(const string& str, size_t* idx=0, int base = 10);

각 매개변수의 의미는 다음과 같다.

  • str - 변환하고자 하는 원본 string
  • idx - 아직 변환되지 않은 부분에서 맨 앞 문자의 인덱스를 가리키는 포인터
  • base - 변환할 수의 밑(진수), 정수형에만 존재하며 10진수가 기본이다. 스트링 내부의 값이 2진수, 8진수, 10진수와 같이 어떠한 진수로 표시되었는지를 나타낸다.

    stoi("11", 0, 2)→11은 2진수로 3이다→int 타입의 값 3으로 변환


5) std::string_view

스트링을 표현하기 위한 방법은 크게 두 가지로 구분할 수 있다.(리터럴은 제외했다)

  • const char*
  • std::string

프로그램을 작성하려면 중간에 변환 과정을 추가하거나, 양쪽 모두에 대응하게끔 오버로딩을 하곤 했었다. 얼마나 불편한가? 그래서 C++17부터 string_view를 제공한다.

string_view extractExtention(string_view fileName) {
	return fileName.substr(fileName.rfind('.'));
}

int main() {
	string fileName_1 = R"(c:\temp\myfile.ext)";
	cout << "C++ : " << extractExtention(fileName_1) << endl;

	const char* fileName_2 = R"(c:\temp\myfile.ext)";
	cout << "C string : " << extractExtention(fileName_2) << endl;

	cout << "Literal : " << extractExtention(R"(c:\temp\myfile.ext)") << endl;
} 

fileName으로 정의된 함수의 매개변수가 string, const char*, literal 모두를 받아들이는 모습이다.

그리고 string_view를 매개변수나 리턴 타입으로 설정하면 포인터와 길이만을 값으로 주고 받는다.

string_view returnSV = extractExtention(R"(c:\temp\myfile.ext)");
cout << returnSV << endl;

Untitled

string_view는 string대신에 사용 가능하다. c_str()의 제외와 몇 가지 메소드의 추가 이외에는 인터페이스가 동일하기 때문이다. 다만 둘을 서로 연결/결합할 수 없다.

string_view sv = "This is string_view";
string str = "This is string";
str = str + sv;          // X
str = str + sv.data();   // O
str = str + string(sv);  // O

6) 정리

나중에 문제가 발생했을 때 해결하려 하지 말고, 프로젝트 계획 단계에서 어떤 string을 사용할 건지 팀과 상의하자.

참고 자료