const std :: string &를 매개 변수로 전달하던 시절이 끝났습니까?


604

나는 그 이유가 통과 할 것을 제안 허브 셔터의 최근 이야기 듣고 std::vectorstd::string에 의해 const &크게 사라를. 그는 이제 다음과 같은 함수를 작성하는 것이 바람직하다고 제안했습니다.

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

return_val함수가 반환하는 시점에서는 rvalue가 될 것이므로 매우 저렴한 이동 의미를 사용하여 반환 할 수 있음을 이해합니다 . 그러나 inval여전히 참조 크기 (보통 포인터로 구현 됨)보다 훨씬 큽니다. std::string힙에 대한 포인터와 char[]짧은 문자열 최적화를위한 멤버 를 포함하여 다양한 구성 요소 가 있기 때문 입니다. 따라서 참조로 전달하는 것이 여전히 좋은 생각 인 것 같습니다.

왜 Herb가 이런 말을했는지 설명해 줄 수 있습니까?


89
이 질문에 대한 가장 좋은 대답은 아마도 C ++ Next에서 Dave Abrahams의 기사 를 읽는 것입니다 . 나는 주제에 맞지 않거나 건설적이지 않은 이것에 대해서는 아무것도 볼 수 없다고 덧붙였다. 프로그래밍에 대한 사실적인 질문이있는 명확한 질문입니다.
Jerry Coffin

흥미 롭기 때문에 어쨌든 사본을 만들어야 할 경우 값별 전달이 참조 별 전달보다 빠를 수 있습니다.
Benj

3
@Sz. 질문이 중복으로 잘못 분류되어 닫히는 것에 민감합니다. 이 사례에 대한 세부 정보가 기억 나지 않으며 재검토하지 않았습니다. 대신 실수를했다는 가정에 대한 내 의견을 삭제하려고합니다. 이것을 주목 해 주셔서 감사합니다.
Howard Hinnant

2
@HowardHinnant, 정말 고마워요.이 세심한 배려와 감성의 수준에 도달 할 때 항상 소중한 순간입니다. 매우 상쾌합니다! (물론 삭제하겠습니다.)
Sz.

답변:


393

Herb가 그가 말한 것을 말한 이유는 이와 같은 경우 때문입니다.

하자 내가 기능이 있다고 A함수를 호출 B함수를 호출하는을 C. 그리고 A를 통해 문자열을 전달 B하고로 C. A모르거나 신경 쓰지 않는다 C. 모두 A에 대해 아는 것입니다 B. 즉, C의 구현 세부 사항입니다 B.

A가 다음과 같이 정의되었다고 가정 해 봅시다.

void A()
{
  B("value");
}

B와 C가 문자열을 const&다음과 같이 사용하면 다음과 같이 보입니다.

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

모든 것이 좋고 좋습니다. 당신은 단지 포인터를 전달하고, 복사하지 않고, 움직이지 않으며, 모두 행복합니다. CA는 소요 const&가 문자열을 저장하지 않기 때문에. 단순히 사용합니다.

이제 간단한 변경을 원합니다 C. 어딘가에 문자열을 저장해야합니다.

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

안녕하세요, 복사 생성자와 잠재적 인 메모리 할당 (SSO (Short String Optimization) 무시 ). C ++ 11의 이동 의미론은 불필요한 복사 구성을 제거 할 수있게 되었습니까? 그리고 A일시적으로지나갑니다. 데이터 C복사 해야하는 이유 는 없습니다 . 주어진 것에 절대로 반대해야합니다.

그것을 제외하고는. 왜냐하면 const&.

C값을 기준으로 매개 변수를 사용하도록 변경 하면 해당 매개 변수에 B복사가 수행됩니다. 나는 아무것도 얻지 못한다.

따라서 str모든 기능을 통해 값을 전달 std::move하고 데이터를 섞는 데 의존 하면이 문제가 발생하지 않습니다. 누군가가 붙잡고 싶다면 그렇게 할 수 있습니다. 그들이 그렇지 않으면, 오 잘.

더 비쌉니까? 예; 값으로 이동하는 것은 참조를 사용하는 것보다 비쌉니다. 사본보다 저렴합니까? SSO가있는 작은 문자열에는 적합하지 않습니다. 그만한 가치가 있습니까?

사용 사례에 따라 다릅니다. 메모리 할당이 얼마나 싫어합니까?


2
값으로 이동하는 것이 참조를 사용하는 것보다 비싸다고 말할 때, 여전히 이동하는 문자열의 길이에 관계없이 일정한 양만큼 더 비쌉니다.
Neil G

3
@NeilG : "구현 의존"의 의미를 이해하십니까? SSO가 구현 되었는지 여부와 방법 에 따라 달라지는 말이 잘못되었습니다 .
ildjarn

17
@ildjarn : 순서 분석에서 최악의 상황이 상수에 묶여 있으면 여전히 일정한 시간입니다. 가장 긴 작은 줄이 없습니까? 해당 문자열을 복사하는 데 일정한 시간이 걸리지 않습니까? 작은 문자열을 모두 복사하는 데 시간이 덜 걸리지 않습니까? 그런 다음 작은 문자열을 복사하는 데 시간이 많이 걸리더라도 작은 문자열의 문자열 복사는 순서 분석에서 "일정한 시간"입니다. 순서 분석은 점근 적 행동 과 관련이 있습니다 .
Neil G

8
@NeilG : 물론 이죠,하지만 당신의 원래 질문은 "이었다 (이동되는 문자열의 길이의 독립) 일정한 양만큼 여전히 잘 더 비싼 것을? "내가 만들려고 점은, 그것은 더 많은 비용이 수 다른 문자열의 길이에 따라 일정한 양으로 "no"로 요약됩니다.
ildjarn

13
moved값 기준으로 문자열이 B에서 C로 바뀌는 이유는 무엇 입니까? B가 B(std::string b)있고 C가 C(std::string c)그렇다면 종료 할 때까지 C(std::move(b))B 를 호출 하거나 b변경되지 않은 상태 ( '이동하지 않은 상태')를 유지해야 B합니다. (아마도 최적화 컴파일러에서 문자열을 이동으로-경우 규칙 경우가 b호출 후 사용하지 않는하지만 강력한 보장이 생각하지 않습니다.) 동일이의 사본에 대한 사실 strm_str. 함수 매개 변수가 rvalue로 초기화 된 경우에도 함수 내부의 lvalue이며 해당 lvalue std::move에서 이동해야합니다.
Pixelchemist

163

const std :: string &를 매개 변수로 전달하던 시절이 끝났습니까?

없음 . 많은 사람들이이 적용되는 도메인을 넘어 (데이브 아브라함 포함)이 조언을하고,에 적용을 단순화 모든 std::string 매개 변수 - 항상 전달 std::string값으로하는 것은 "가장 좋은 방법은"일체의 임의의 매개 변수 및 응용 프로그램되지 않습니다 최적화이 때문에 대화 / 문서에 중점을두고 제한된 사례에만 적용 됩니다 .

값을 반환하거나 매개 변수를 변경하거나 값을 가져 오는 경우 값을 전달하면 값 비싼 복사가 절약되고 구문상의 편의가 제공됩니다.

항상 const 참조를 전달 하면 copy가 필요하지 않을 때 많은 복사 절약 됩니다 .

이제 구체적인 예를 보자.

그러나 inval은 여전히 ​​참조의 크기 (보통 포인터로 구현 됨)보다 훨씬 큽니다. std :: string에는 힙에 대한 포인터 및 짧은 문자열 최적화를위한 멤버 char []를 포함하여 다양한 구성 요소가 있기 때문입니다. 따라서 참조로 전달하는 것이 여전히 좋은 생각 인 것 같습니다. 왜 Herb가 이런 말을했는지 설명해 줄 수 있습니까?

스택 크기가 중요한 경우 (및 이것이 인라인 / 최적화되지 않았다고 가정) return_val+ inval> return_val-IOW 일 경우 최대 스택 사용량을 여기에 값으로 전달하여 줄일 수 있습니다 (주 : ABI의과 단순화). 한편 const 참조를 전달하면 최적화가 비활성화 될 수 있습니다. 여기서 주된 이유는 스택 성장을 피하는 것이 아니라 적용 가능한 곳 에서 최적화를 수행 할 수 있도록 하는 것입니다 .

const 참조로 지나간 시대는 끝나지 않았습니다. 규칙은 예전보다 훨씬 복잡합니다. 성능이 중요한 경우 구현에 사용하는 세부 사항을 기반으로 이러한 유형을 전달하는 방법을 고려하는 것이 좋습니다.


3
스택 사용시 일반적인 ABI는 스택 사용없이 레지스터에서 단일 참조를 전달합니다.
ahcox

63

이것은 컴파일러의 구현에 크게 의존합니다.

그러나 사용하는 내용에 따라 다릅니다.

다음 기능을 고려하자.

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

이러한 함수는 인라인을 피하기 위해 별도의 컴파일 단위로 구현됩니다. 그런 다음 :
1.이 두 함수에 리터럴을 전달하면 성능에 큰 차이가 없습니다. 두 경우 모두 문자열 객체를 만들어야합니다
. 2. 다른 std :: string 객체를 전달 하면 딥 카피를 수행 하므로 foo2성능이 뛰어 납니다.foo1foo1

내 PC에서 g ++ 4.6.1을 사용하여 다음과 같은 결과를 얻었습니다.

  • 참조로 변수 : 1000000000 반복-> 경과 시간 : 2.25912 초
  • 값으로 변수 : 1000000000 반복-> 경과 시간 : 27.2259 초
  • 참조로 리터럴 : 100000000 반복-> 경과 시간 : 9.10319 초
  • 리터럴 값 : 100000000 회 반복-> 경과 시간 : 8.62659 초

4
더 관련이있는 것은 함수 내부 에서 일어나는 일 입니다. 참조로 호출되는 경우 값으로 전달 할 때 생략 할 수있는 내부 사본을 만들어야 합니까?
leftaroundabout

1
@leftaroundabout 예, 물론입니다. 두 기능이 똑같은 일을하고 있다고 가정합니다.
BЈовић

5
그건 내 요점이 아니야 값 또는 참조로 전달하는 것이 더 나은지 여부는 함수 내부에서 수행하는 작업에 따라 다릅니다. 귀하의 예에서는 실제로 많은 문자열 객체를 사용하지 않으므로 참조가 분명히 좋습니다. 그러나 함수의 작업이 문자열을 구조체에 배치하거나 문자열의 여러 스플릿을 포함하는 일부 재귀 알고리즘을 수행하여 값 으로 전달하면 참조로 전달하는 것과 비교하여 실제로 일부 복사를 저장할 수 있습니다 . Nicol Bolas는 그것을 잘 설명합니다.
leftaroundabout

3
나에게 "그것은 당신이 함수 안에서 무엇을하는지에 달려있다"는 나쁜 디자인이다-당신은 구현의 내부에 함수의 서명을 기반으로하기 때문에.
한스 올슨

1
오타 일 수도 있지만 마지막 두 문자 타이밍에는 10 배 적은 루프가 있습니다.
TankorSmash

54

짧은 대답 : NO! 긴 대답 :

  • 문자열을 수정하지 않으면 (처리는 읽기 전용)으로 전달하십시오 const ref&.
    (물론 const ref&그것을 사용하는 함수가 실행되는 동안 분명히 범위 내에 있어야합니다)
  • 수정하거나 범위 (스레드) 를 벗어나는 것을 알고 있다면으로 전달하십시오 . 함수 본문 내부를 value복사하지 마십시오 const ref&.

cpp-next.com에 "Want speed, pass by value!" 라는 게시물이 있습니다 . . TL; DR :

지침 : 함수 인수를 복사하지 마십시오. 대신 값으로 전달하고 컴파일러가 복사를 수행하게하십시오.

^의 번역

함수 인수를 복사하지 마십시오 --- 의미 : 인수 값을 내부 변수에 복사하여 인수 값을 수정하려는 경우 value 인수를 대신 사용하십시오 .

따라서이 작업을 수행하지 마십시오 .

std::string function(const std::string& aString){
    auto vString(aString);
    vString.clear();
    return vString;
}

이것을하십시오 :

std::string function(std::string aString){
    aString.clear();
    return aString;
}

함수 본문에서 인수 값을 수정해야 할 때

함수 본문에서 인수를 사용하는 방법을 알고 있으면됩니다. 읽기 전용 또는 NOT ... 및 범위 내에있는 경우


2
경우에 따라 참조로 전달하는 것이 좋지만 항상 값으로 전달할 것을 권장하는 지침을 가리 킵니다.
Keith Thompson

3
@KeithThompson 함수 인수를 복사하지 마십시오. 의미는 const ref&내부 변수를 복사하여 수정 하지 않습니다 . 수정이 필요한 경우 ... 매개 변수를 값으로 만드십시오. 영어를 사용하지 않는 사람들에게는 분명합니다.
CodeAngry

5
@KeithThompson Guideline 인용문 (함수 인수를 복사하지 마십시오. 대신 값으로 전달하고 컴파일러에서 복사를 수행하도록하십시오) 는 해당 페이지에서 복사됩니다. 그것이 명확하지 않으면, 나는 도울 수 없습니다. 최선의 선택을 할 컴파일러를 완전히 신뢰하지 않습니다. 함수 인수를 정의하는 방식에서 내 의도에 대해 매우 명확하게 알고 싶습니다. # 1 읽기 전용 인 경우는 const ref&입니다. # 2 쓰거나 범위를 벗어난 것을 알고 있다면 가치를 사용합니다. # 3 원래 값을 수정해야 할 경우을지나갑니다 ref&. # 4 pointers *인수가 선택 사항 인 경우 사용할 수 nullptr있습니다.
CodeAngry

11
나는 가치에 의한 것인지 아니면 참조에 의한 것인지의 문제에 대해서는 편견이 없다. 내 요점은 어떤 경우에는 참조로 전달하는 것을 옹호하지만 항상 가치를 전달하는 것이 좋습니다 지침을 인용하십시오. 지침에 동의하지 않으면 그렇게 말하고 이유를 설명 할 수 있습니다. (cpp-next.com에 대한 링크가 작동하지 않습니다.)
Keith Thompson

4
@ KeithThompson : 당신은 지침을 잘못 낙담하고 있습니다. "항상"값으로 전달하는 것은 아닙니다. 요약하자면, "로컬 복사본을 만들려면 값으로 전달을 사용하여 컴파일러가 해당 복사본을 수행하도록하십시오." 복사본을 만들지 않을 때는 값으로 전달하는 것은 아닙니다.
벤 Voigt

43

실제로 사본이 필요하지 않은 한을 가져가는 것이 합리적 const &입니다. 예를 들면 다음과 같습니다.

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

문자열을 값으로 가져 오도록 변경하면 매개 변수를 이동하거나 복사하게 될 필요가 없습니다. 복사 / 이동이 더 비쌀뿐만 아니라 새로운 잠재적 실패를 초래합니다. 복사 / 이동으로 인해 예외가 발생할 수 있습니다 (예 : 복사 중 할당이 실패 할 수 있음). 기존 값을 참조 할 수 없습니다.

사본 필요한 경우 일반적으로 (항상?) 값으로 전달하고 반환하는 것이 가장 좋습니다. 실제로 추가 복사본이 실제로 성능 문제를 일으키는 것을 발견하지 않으면 일반적으로 C ++ 03에서 그것에 대해 걱정하지 않습니다. 복사 제거는 최신 컴파일러에서 매우 안정적으로 보입니다. RVO에 대한 컴파일러 지원 테이블을 확인 해야하는 사람들의 회의론과 주장은 오늘날 대부분 쓸모가 없다고 생각합니다.


간단히 말해 C ++ 11은 복사 제거를 신뢰하지 않는 사람들을 제외하고는 이와 관련하여 아무것도 변경하지 않습니다.


2
이동 생성자는 일반적으로로 구현 noexcept되지만 복사 생성자는 그렇지 않습니다.
leftaroundabout

25

거의.

C ++ 17 basic_string_view<?>에는 std::string const&매개 변수에 대한 기본 사용 사례가 하나 있습니다 .

이동 의미의 존재로 인해 하나의 사용 사례가 제거 std::string const&되었습니다. 매개 변수를 저장하려는 경우 매개 변수를 사용할 수 있으므로 std::stringby 값을 사용하는 것이 더 최적 move입니다.

누군가가 원시 C로 함수를 호출 "string"하면 이는 std::string두 개의 버퍼가 아니라 하나의 버퍼 만 할당 됨을 의미합니다 std::string const&.

그러나 복사를하지 않으려는 경우 std::string const&C ++ 14에서 가져 오는 것이 여전히 유용합니다.

를 사용하면 std::string_viewC 스타일로 '\0'끝나는 문자 버퍼 가 필요한 API에 해당 문자열을 전달 std::string하지 않는 한 할당 위험없이 더 효율적으로 기능을 수행 할 수 있습니다 . 원시 C 문자열은 std::string_view할당이나 문자 복사없이 로 변환 할 수도 있습니다 .

이 시점 std::string const&에서 데이터 도매를 복사하지 않고 널 종료 버퍼를 예상하는 C 스타일 API로 전달할 때 사용하며 더 높은 수준의 문자열 함수가 필요 std::string합니다. 실제로 이것은 드문 요구 사항입니다.


2
이 답변에 감사드립니다. 그러나 약간의 도메인 별 편견으로 인해 (많은 품질 답변처럼) 어려움을 겪고 있음을 지적하고 싶습니다. 재치 :“실제로, 이것은 드문 요구 사항입니다.”… 내 자신의 개발 경험에서, 저자에게 비정상적으로 좁아 보이는 이러한 제약 조건은 문자 그대로 항상 충족됩니다. 이것을 지적 할 가치가 있습니다.
fish2000

1
@ fish2000 명확하게 말해서, std::string지배 하기 위해서는 이러한 요구 사항 중 일부만 이 아니라 모든 요구 사항이 필요 합니다. 그 중 하나 또는 두 개는 일반적입니다. 어쩌면 당신은 일반적으로 모든 3 (같은, 당신은 당신이 도매로 전달하려고하는 C API 선택하는 문자열 인수의 몇 가지 분석을하는거야?) 필요합니까
Yakk - 아담 Nevraumont

@ Yakk-AdamNevraumont YMMV 일이지만 POSIX 또는 C- 문자열 의미가 가장 낮은 공통 분모와 같은 다른 API에 대해 프로그래밍하는 경우 빈번한 유스 케이스입니다. "내가 std::string_view원 C 문자열을 할당이나 문자 복사없이 std :: string_view로 바꿀 수있다"는 점에서 내가 정말 좋아한다고 말해야한다. 이는 C ++을 사용하는 사람들에게 실제로 이러한 API 사용.
fish2000

1
@ fish2000 " '원시 C 문자열은 할당이나 문자 복사없이 std :: string_view로 바꿀 수 있습니다.' 실제로, 그러나 가장 좋은 부분은 생략합니다. 원시 문자열이 문자열 리터럴 인 경우 런타임 strlen ()조차 필요하지 않습니다 !
돈 해치

17

std::string일반 데이터 가 아님 (POD) , 그 원시 크기는 지금까지 가장 관련있는 일이 아니다. 예를 들어, SSO 길이보다 길고 힙에 할당 된 문자열을 전달하면 복사 생성자가 SSO 스토리지를 복사하지 않을 것으로 예상됩니다.

이것이 권장되는 이유 inval는 인수 표현식으로 구성되었으므로 항상 적절하게 이동 또는 복사되므로 인수의 소유권이 필요하다고 가정 할 때 성능 손실이 없습니다. 그렇지 않으면 const참조가 더 나은 방법 일 수 있습니다.


2
복사 생성자에 대한 흥미로운 점은 SSO를 사용하지 않는 경우 SSO를 걱정할 정도로 똑똑합니다. 아마 맞습니다, 그것이 사실인지 확인해야합니다 ;-)
Benj

3
@Benj : 내가 알고있는 오래된 의견이지만, SSO가 충분히 작은 경우 조건부 분기를 수행하는 것보다 무조건 복사하는 것이 빠릅니다. 예를 들어, 64 바이트는 캐시 라인이며 실제로 사소한 시간에 복사 할 수 있습니다. x86_64에서 아마도 8주기 이하일 것입니다.
Zan Lynx

SSO가 복사 생성자에 의해 복사되지 않더라도 std::string<>스택에서 할당 된 32 바이트는 16 바이트가 초기화되어야합니다. 이것을 참조를 위해 할당되고 초기화 된 8 바이트와 비교해보십시오. CPU 작업량의 두 배이며 다른 데이터에서는 사용할 수없는 캐시 공간의 4 배를 차지합니다.
cmaster-복원 monica

아, 그리고 레지스터에서 함수 인수를 전달하는 것에 대해 이야기하는 것을 잊었습니다. 그 ... 마지막 호출자에 대해 0까지 참조의 스택 사용을 가져올 것
cmaster - 분석 재개 모니카

16

이 질문에 대한 답변을 여기에 복사 / 붙여 넣었으며이 질문 에 맞게 이름과 철자를 변경했습니다.

요청되는 내용을 측정하는 코드는 다음과 같습니다.

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

나를 위해이 출력 :

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

아래 표에는 내 결과가 요약되어 있습니다 (clang -std = c ++ 11 사용). 첫 번째 숫자는 사본 구성 수이고 두 번째 숫자는 이동 구성 수입니다.

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

값별 솔루션은 하나의 과부하 만 필요하지만 lvalue 및 xvalue를 전달할 때 추가 이동 구성이 필요합니다. 이것은 주어진 상황에서 허용되거나 허용되지 않을 수 있습니다. 두 솔루션 모두 장단점이 있습니다.


1
std :: string은 표준 라이브러리 클래스입니다. 이미 이동 가능하고 복사 가능합니다. 이것이 어떻게 관련이 있는지 모르겠습니다. OP는 이동 대 복사의 성능이 아니라 이동 대 참조 의 성능에 대해 더 많은 것을 요구하고 있습니다.
Nicol Bolas

3
이 답변은 이동 횟수를 계산하고 std :: string이 Herb와 Dave가 설명하는 값으로 전달되는 설계에 따라 오버로드 된 함수 쌍을 참조하여 전달합니다. 데모에서 OP의 코드를 사용하지만 복사 / 이동 될 때 더미 문자열을 사용하여 소리 지르지 않습니다.
Howard Hinnant

테스트를 수행하기 전에 코드를 최적화해야합니다…
상자성 크로와상

3
@TheParamagneticCroissant : 다른 결과를 얻었습니까? 그렇다면 어떤 명령 줄 인수로 어떤 컴파일러를 사용합니까?
하워드 힌 넌트

14

Herb Sutter는 Bjarne Stroustroup과 함께 const std::string&매개 변수 유형으로 권장 하고 있습니다. https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in을 참조 하십시오 .

여기에 다른 답변에는 언급되지 않은 함정이 있습니다. 문자열 리터럴을 const std::string&매개 변수에 전달하면 리터럴의 문자를 보유하기 위해 즉시 생성 된 임시 문자열에 대한 참조를 전달합니다. 그런 다음 해당 참조를 저장하면 임시 문자열이 할당 해제되면 유효하지 않습니다. 안전을 위해 참조가 아닌 사본을 저장해야합니다 . 이 문제는 문자열 리터럴이 const char[N]유형이므로로 승격해야 한다는 사실에서 비롯 됩니다 std::string.

아래 코드는 마이너 효율 옵션과 함께 함정과 해결 방법을 설명 -와 과부하 const char*에 설명 된 바와 같이, 방법 거기 C에서 참조로 문자열 리터럴을 통과 할 수있는 방법을 ++ .

(참고 : Sutter & Stroustroup은 문자열의 복사본을 유지하는 경우 && 매개 변수 및 std :: move ()를 사용하여 오버로드 된 함수를 제공하는 것이 좋습니다.)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

산출:

const char * constructor
const std::string& constructor

Second string
Second string

이것은 다른 문제이며 WidgetBadRef는 const & 매개 변수를 가질 필요가 없습니다. 문제는 WidgetSafeCopy가 문자열 매개 변수를 취한 경우 속도가 느리다는 것입니다. (나는 회원에게 임시로 사본을 찾는 것이 확실히 더 쉽다고 생각한다)
Superfly Jon

참고 T&&항상 보편적 참조 아니다; 실제로, std::string&&유형 공제가 수행되지 않기 때문에 항상 rvalue 참조가되고 절대 참조는되지 않습니다 . 따라서 Stroustroup & Sutter의 조언은 Meyers와 충돌하지 않습니다.
저스틴 타임-복원 모니카

@JustinTime : 감사합니다; 실제로 std :: string &&은 보편적 인 참조가된다는 잘못된 최종 문장을 제거했습니다.
circlepi314

@ circlepi314 천만에요. 그것은 쉬운 혼합 T&&이며, 주어진 어떤 것이 유추 된 보편적 참조인지 아니면 비 감소 된 rvalue 참조 인지 혼동 될 수 있습니다 . 그들은 (예 : 보편적 참조에 대해 다른 기호를 도입하면 아마 명확 했 &&&의 조합으로, &&&),하지만 그건 아마 바보 보일 것이다.
저스틴 타임-복원 모니카

8

C ++ 참조를 사용하는 IMO std::string 는 빠르고 짧은 로컬 최적화이며, 값으로 전달하는 것이 더 나은 글로벌 최적화 일 수 있습니다.

답은 상황에 따라 다릅니다.

  1. 외부에서 내부 함수까지 모든 코드를 작성하는 경우 코드의 기능을 알고 있으면 reference를 사용할 수 있습니다 const std::string &.
  2. 라이브러리 코드를 작성하거나 문자열이 전달되는 라이브러리 코드를 많이 사용하는 경우 std::string복사 생성자 동작 을 신뢰하면 전역 적으로 더 많은 의미를 얻을 수 있습니다.

6

“허브 셔터 (Herb Sutter)“기본으로 돌아 가기! 현대 C ++ 스타일의 필수 요소”를 참조하십시오 . 다른 주제 중에서도 그는 과거에 제공된 매개 변수 전달 조언과 C ++ 11과 함께 제공되는 새로운 아이디어를 검토하고 특히 문자열을 값으로 전달하는 아이디어.

슬라이드 24

벤치 마크에 따르면 std::string함수가 함수를 복사 할 경우 값 을 기준으로 전달 하면 속도가 상당히 느려질 수 있습니다.

const&버전은 기존 문자열을 업데이트하여 이미 할당 된 버퍼를 재사용 할 수있는 반면 항상 전체 사본을 만든 다음 제자리로 이동해야 하기 때문입니다.

슬라이드 27 참조 :“세트”기능의 경우 옵션 1은 항상 동일합니다. 옵션 2는 rvalue 참조를위한 과부하를 추가하지만 여러 매개 변수가있는 경우 조합 폭발이 발생합니다.

값별 전달 트릭이 유효한 문자열을 작성해야하는 (싱크 값) 매개 변수에만 해당됩니다. 즉, 생성자 매개 변수가 일치 유형의 멤버를 직접 초기화하는 입니다.

당신이 걱정에 갈 수있는 방법을 깊이보고 싶다면, 시청 니콜라 Josuttis에의 그것과 프리젠 테이션과 행운을 ( "퍼펙트! - 완료" n은 이전 버전의 오류를 발견 한 후 시간 적이 있었다.?)


이는 표준 지침에서 ⧺F.15 로 요약됩니다 .


3

JDługosz이 코멘트에 지적 @ 허브는 (? 이상) 또 다른 조언을 제공으로 이야기를 여기에서 약 참조 : https://youtu.be/xnqTKD8uD64?t=54m50s .

그의 조언 f은 싱크 인수에서 구문을 이동한다고 가정 할 때 소위 싱크 인수를 취하는 함수에 값 매개 변수를 사용하는 것으로 요약됩니다 .

이 일반적인 접근 방식 f은 lvalue 및 rvalue 인수에 각각 최적화 된 구현과 비교하여 lvalue 및 rvalue 인수 모두에 대해 이동 생성자의 오버 헤드 만 추가합니다 . 이것이 왜 그런지 보려면 f, T복사 및 이동 구성 가능한 유형 이있는 value 매개 변수를 사용 한다고 가정하십시오 .

void f(T x) {
  T y{std::move(x)};
}

호출 f복사 생성자가 발생합니다 좌변 인수는 구조에 호출되고 x, 및 이동 생성자는 구성하기 위해 호출되는 y. 반면에 frvalue 인수를 호출 하면 이동 생성자가 구성하기 위해 호출 x되고 다른 이동 생성자가 호출되어 생성됩니다.y 됩니다.

일반적으로 flvalue 인수 에 대한 최적의 구현은 다음과 같습니다.

void f(const T& x) {
  T y{x};
}

이 경우 하나의 복사 생성자 만 생성하여 호출됩니다 y. frvalue 인수 에 대한 최적의 구현은 일반적으로 다음과 같습니다.

void f(T&& x) {
  T y{std::move(x)};
}

이 경우 하나의 이동 생성자 만 호출되어 생성됩니다. y .

따라서 합리적인 타협은 가치 매개 변수를 취하고 최적의 구현과 관련하여 lvalue 또는 rvalue 인수를 하나 더 이동 생성자가 호출하는 것입니다.

@ JDługosz가 주석에서 지적했듯이 값을 전달하면 싱크 인수에서 일부 객체를 구성하는 함수에만 적합합니다. f인수를 복사 하는 함수가있는 경우 값별 접근 방식은 일반적인 통과 기준 접근 방식보다 더 많은 오버 헤드를 갖습니다. f매개 변수의 사본을 보유하는 함수 에 대한 값별 접근 방식 은 다음과 같습니다.

void f(T x) {
  T y{...};
  ...
  y = std::move(x);
}

이 경우, lvalue 인수에 대한 복사 구성 및 이동 지정과 rvalue 인수에 대한 이동 구성 및 이동 지정이 있습니다. lvalue 인수에 가장 적합한 경우는 다음과 같습니다.

void f(const T& x) {
  T y{...};
  ...
  y = x;
}

이것은 할당으로 만 귀결되는데, 이는 복사 생성자보다 값이 저렴하고 값으로 전달하는 방법에 필요한 이동 할당보다 훨씬 저렴합니다. 할당이 기존의 할당 된 메모리를 재사용 할 수 있기 때문입니다.y 방지 할 수있는 반면, 복사 생성자는 일반적으로 메모리를 할당하기 때문입니다.

rvalue 인수의 f경우 사본을 보유 하기위한 가장 최적의 구현 은 다음 형식을 갖습니다.

void f(T&& x) {
  T y{...};
  ...
  y = std::move(x);
}

따라서이 경우 이동 할당 만 가능합니다. fconst 값 을 갖는 버전으로 rvalue를 전달하면 이동 할당 대신 할당 비용 만 발생합니다. 상대적으로 말하자면f 이 경우 일반적인 구현으로 const 참조 취하는 이 바람직합니다.

따라서 일반적으로 가장 최적의 구현을 위해서는 대화에서 볼 수 있듯이 오버로드하거나 일종의 완벽한 전달을 수행해야합니다. 단점은 f인수의 값 범주에서 과부하를 선택하는 경우 매개 변수의 수에 따라 필요한 과부하 수의 조합 폭발입니다 . 퍼펙트 포워딩은 f템플릿 기능이되는 단점이 있어, 가상으로 만드는 것을 막고, 100 % 올바른 결과를 얻으려면 훨씬 더 복잡한 코드를 생성합니다.


새로운 답변에서 Herb Sutter의 결과를 참조하십시오 : 이동 할당하지 않고 구성 을 이동할 때만 수행하십시오 .
JDługosz

1
@ JDługosz, Herb의 대화에 대한 포인터에 감사드립니다. 방금 그것을보고 내 대답을 완전히 수정했습니다. 나는 (이동) 할당 조언을 ​​몰랐다.
톤 반 덴 Heuvel

그 수치와 조언은 이제 표준 지침 문서 에 있습니다.
JDługosz

1

문제는 "const"는 과립 화되지 않은 규정 자입니다. 일반적으로 "const string ref"의 의미는 "참조 문자열을 수정하지 마십시오"가 아니라 "이 문자열을 수정하지 마십시오"입니다. C ++에서는 어떤 멤버가 "const" 인지 말할 방법이 없습니다 . 그들은 모두 존재하거나 그들 중 어느 것도 아닙니다.

이 언어 문제를 해킹하기 위해 STL 예제의 "C ()"가 어쨌든 이동 의미 사본을 만들 도록 허용하고 참조 횟수와 관련하여 "const"를 무시합니다 (변경 가능). 잘 지정되어 있으면 괜찮을 것입니다.

STL이 아니기 때문에 const_casts <> 참조 카운터를 멀어지게하는 문자열 버전이 있습니다 (클래스 계층 구조에서 어떤 것을 소급 적으로 변경할 수있는 방법은 없습니다). 누출이나 문제없이 하루 종일 심오한 기능으로 사본을 만들 수 있습니다.

C ++은 여기서 "파생 클래스 const 입도"를 제공하지 않기 때문에, 좋은 스펙을 작성하고 반짝이는 새로운 "const moving string"(cmstring) 객체를 만드는 것이 내가 본 최고의 솔루션입니다.


@BenVoigt yep ... 캐스팅하지 않고 변경 가능해야하지만 파생 클래스에서 STL 멤버를 변경 가능으로 변경할 수는 없습니다.
Erik Aronesty
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.