HOWTO · C++

C++의 함수에서 문자열을 반환하는 방법

이 기사에서는 C++의 함수에서 문자열을 반환하는 방법을 보여줍니다.

이 페이지의 내용

일반적인 C++ 함수에서는 std::string를 값으로 반환하세요. 그러면 호출자는 독립된 문자열을 받고, 가능한 경우 C++가 반환값 최적화를 적용할 수 있습니다. 수명을 다른 곳에서 관리하는 기존 객체를 반환할 때만 참조를 사용하고, 포인터나 C 스타일 문자열은 인터페이스에서 특별히 요구할 때만 사용하세요. 아래 예제는 C++11 이상을 기준으로 합니다.

문자열을 값으로 반환하기

함수에서 문자열을 만들거나 변환할 때는 값을 반환하는 것이 가장 명확한 기본 방법입니다. 호출자는 자신만의 문자열 값을 받아 독립적으로 저장하거나 변경할 수 있습니다. 이름이 있는 지역 변수를 반환하는 방식은 보통 효율적입니다. 컴파일러는 명명된 반환값 최적화를 통해 결과를 호출자의 저장 공간에 직접 생성할 수 있습니다. 이 최적화가 적용되지 않으면 std::string를 이동할 수 있습니다. 어느 경우에도 반환문에 std::move를 추가할 필요는 없습니다.

다음 예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#include <iostream>
#include <string>

std::string makeGreeting() {
    std::string result = "Hello, C++!";
    return result;
}

int main() {
    std::string greeting = makeGreeting();
    std::cout << greeting << '\n';
}

함수의 반환 형식은 std::string이고, return result;는 지역 변수의 값을 반환합니다. 함수가 끝나면 지역 변수는 사라지지만 반환된 문자열은 호출자가 계속 사용할 수 있습니다. 동적으로 할당한 문자열의 소유권을 따로 관리하는 것보다 값을 반환하는 편이 대체로 간단하고 안전합니다.

기존 문자열을 참조로 반환하기

참조 반환은 이미 존재하는 객체를 별칭으로 가리킬 뿐, 새 문자열을 만들지는 않습니다. 수명이 더 긴 객체가 소유한 문자열을 함수가 선택하거나 공개할 때 유용할 수 있습니다. 호출자는 같은 객체를 보므로 비상수 참조를 통해 변경하면 원래 객체도 바뀝니다. 호출자가 수정하지 않고 읽기만 해야 한다면 const std::string&를 사용하세요.

반환된 참조를 사용할 때마다 참조 대상 객체가 살아 있어야 합니다. 지역 변수에 대한 참조를 반환하지 마세요. 함수가 끝날 때 해당 변수가 파괴되어 호출자에게 댕글링 참조가 남습니다. 참조 매개변수는 다음 예제처럼 호출자가 인수의 수명을 유지하는 경우에만 안전하게 반환할 수 있습니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#include <iostream>
#include <string>

const std::string& sameString(const std::string& value) {
    return value;
}

int main() {
    std::string original = "Existing string";
    const std::string& alias = sameString(original);
    std::cout << alias << '\n';
}

문자열을 포인터로 반환하기

포인터 반환은 선택적 접근을 표현하거나 기존 인터페이스를 따를 때 사용할 수 있지만, 수명과 소유권도 계약의 일부가 됩니다. 기존 문자열의 주소를 반환하려면 포인터를 사용하는 모든 기간 동안 그 문자열이 살아 있을 때만 그렇게 하세요. 객체의 소유자가 누구인지, 포인터가 null일 수 있는지 문서화하세요.

반환할 목적으로만 std::string를 new로 할당하지 마세요. 그러면 호출자가 직접 삭제해야 하고, 조기 반환이나 오류를 처리하기도 더 어려워집니다. 새 문자열을 만들 때는 값으로 반환하세요. 아래 예제는 기존 객체를 가리키며, 포인터를 사용하는 동안 그 객체가 살아 있도록 합니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#include <iostream>
#include <string>

const std::string* findGreeting(const std::string& value) {
    return &value;
}

int main() {
    std::string greeting = "Hello through a pointer";
    const std::string* result = findGreeting(greeting);
    if (result != nullptr) {
        std::cout << *result << '\n';
    }
}

이 함수는 value의 소유권을 가져오지 않습니다. 참조 대상 문자열이 파괴된 뒤에도 호출자가 반환된 포인터를 보관해서는 안 됩니다.

도움이 될 때만 std::move 사용하기

std::move는 이동 연산을 허용하는 캐스트이며, 그 자체로 데이터를 옮기지는 않습니다. 반환문에 return std::move(local);을 쓰는 것은 대개 불필요합니다. 이 표현은 명명된 반환값 최적화를 막아 직접 생성 대신 이동이 일어나게 할 수도 있습니다. 지역 변수의 결과에는 return local;을 사용하고, 적용 가능한 최적화는 언어와 컴파일러에 맡기세요.

자동 이동 처리를 적용할 수 없는 객체에서 값을 넘길 때는 명시적 이동이 유용합니다. 예를 들어 수명이 더 긴 객체의 멤버를 밖으로 이동해야 하는 경우가 그렇습니다. 이렇게 하면 원본 객체가 바뀌므로, 이전 상태의 원본이 더는 필요하지 않을 때만 사용하세요. 반환이 더 빨라 보이게 하려고 std::move를 덧붙이지 마세요.

char*로 C 스타일 문자열 반환하기

C 스타일 문자열은 null로 끝나는 문자 시퀀스이며 std::string는 아닙니다. API에서 문자 포인터가 필요하고 결과가 고정된 텍스트라면 문자열 리터럴을 가리키는 const char*를 반환하세요. 문자열 리터럴은 정적 저장 기간을 가지므로 포인터는 프로그램이 실행되는 동안 유효합니다. 여기서 const char*는 수정할 수 없는 문자를 가리키는 포인터입니다. 따라서 그 포인터를 통해 문자를 수정할 수는 없지만, 포인터 자체는 const가 아니므로 다른 곳을 가리키도록 다시 지정할 수 있습니다. 이는 수정 가능한 문자를 가리키는 const 포인터인 char* const와 다릅니다. 문자열 리터럴 자체를 수정해서는 안 됩니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#include <iostream>

const char* greeting() {
    return "Hello from C-style text";
}

int main() {
    std::cout << greeting() << '\n';
}

함수가 반환될 때 사라지는 지역 문자 배열에 대한 포인터를 반환하지 마세요. 지역 std::string 내부를 가리키는 포인터도 반환하지 마세요. c_str()의 결과는 std::string 객체의 수명에 종속되며, 해당 문자열을 수정하는 작업으로 무효화될 수 있습니다. 호출자가 독립적으로 소유하고 변경할 수 있는 결과를 원한다면 std::string를 반환하세요.