C ++ 11에서 값별 전달이 합리적인 기본값입니까?


142

전통적인 C ++에서는 값을 함수와 메소드에 전달하는 것이 큰 객체의 경우 속도가 느리고 일반적으로 눈살을 찌푸립니다. 대신 C ++ 프로그래머는 참조를 전달하는 경향이 있지만 속도는 빠르지 만 소유권과 특히 메모리 관리 (객체가 힙 할당되는 경우)와 관련된 모든 종류의 복잡한 질문을 유발합니다.

이제 C ++ 11에는 Rvalue 참조와 이동 생성자가 있습니다. 즉, std::vector값으로 전달되거나 함수 외부로 값 이 큰 대형 객체 (예 :)를 구현할 수 있습니다.

따라서 이것이 기본값이 std::vectorand std::string? 와 같은 유형의 인스턴스에 대해 값으로 전달되어야 함을 의미 합니까? 사용자 정의 개체는 어떻습니까? 새로운 모범 사례는 무엇입니까?


22
pass by reference ... which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated). 소유권이 얼마나 복잡하거나 문제가되는지 모르겠습니다. 내가 뭔가를 놓친 것일 수 있습니까?
iammilind

1
@iammilind : 개인적인 경험의 예. 하나의 스레드에는 문자열 객체가 있습니다. 다른 스레드를 생성하는 함수로 전달되지만 호출자에게 알려지지 않은 함수는 문자열이 const std::string&아닌 사본을 가져 왔습니다. 첫 번째 스레드가 종료되었습니다 ...
Zan Lynx

12
@ 잔 린스 (ZanLynx) : 쓰레드 함수라고 부르지 않는 함수처럼 들린다.
Nicol Bolas

5
iammilind에 동의하면 아무런 문제가 없습니다. "큰"객체의 경우 const 참조로 전달하고 작은 객체의 경우 값으로 전달해야합니다. 나는 약 16 바이트 (또는 32 비트 시스템에서 4 개의 포인터)에서 크고 작은 사이에 한계를 두었습니다.
JN

3
허브 셔터의 기본으로 돌아 가기! CppCon에서 현대 C ++ 프레젠테이션의 필수 사항은 이에 대해 상당히 자세히 설명했습니다. 여기 비디오 .
Chris Drew

답변:


138

본문 내부에 사본을 만들어야하는 경우 합리적인 기본값 입니다. 이것이 Dave Abrahams 가 옹호하는 내용입니다 .

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

코드에서 이것은 이것을 수행하지 않는다는 것을 의미합니다.

void foo(T const& t)
{
    auto copy = t;
    // ...
}

그러나 이것을하십시오 :

void foo(T t)
{
    // ...
}

호출자가 다음 foo과 같이 사용할 수있는 이점이 있습니다 .

T lval;
foo(lval); // copy from lvalue
foo(T {}); // (potential) move from prvalue
foo(std::move(lval)); // (potential) move from xvalue

최소한의 작업 만 수행됩니다. 당신은 참조와 동일한 기능을 수행 할 두 개의 오버로드가 필요 거라고 void foo(T const&);하고 void foo(T&&);.

이를 염두에두고 이제는 다음과 같이 소중한 생성자를 작성했습니다.

class T {
    U u;
    V v;
public:
    T(U u, V v)
        : u(std::move(u))
        , v(std::move(v))
    {}
};

그렇지 않으면 참조로 const전달해도 여전히 합리적입니다.


29
+1, 특히 마지막 비트의 경우 :) Move Constructors는 이동할 객체가 나중에 변경되지 않을 것으로 예상되는 경우에만 호출 할 수 있다는 것을 잊지 않아야합니다 SomeProperty p; for (auto x: vec) { x.foo(p); }. 또한 Move Constructors는 비용이 들지만 (객체가 클수록 비용이 높음) const&기본적으로 무료입니다.
Matthieu M.

25
@MatthieuM. 그러나 이동의 "객체가 클수록 비용이 높다"는 것이 실제로 무엇을 의미하는지 아는 것이 중요합니다. "큰"은 실제로 "더 많은 구성원 변수"를 의미합니다. 예를 들어, std::vector백만 개의 요소로 요소를 이동하는 것은 벡터의 모든 개체가 아니라 힙의 배열에 대한 포인터 만 이동하기 때문에 5 개의 요소로 요소를 이동 하는 것과 같습니다 . 실제로 그렇게 큰 문제는 아닙니다.
Lucas

+1 또한 C ++ 11을 사용하기 시작한 후 pass-by-value-then-move 구문을 사용하는 경향이 있습니다. 내 코드가 이제 std::move모든 곳 에서 사용되기 때문에 다소 불편 함을 느끼게 합니다.
stijn

1
한 번의 위험이 있습니다 const&. void foo(const T&); int main() { S s; foo(s); }. S를 인수로 사용하는 T 생성자가 있으면 형식이 다르더라도 컴파일 할 수 있습니다. 큰 T 객체가 생성 될 수 있으므로 속도가 느려질 수 있습니다. 복사하지 않고 참조를 전달 한다고 생각할 수도 있지만 그럴 수도 있습니다. 더 많은 질문에 대한 답변을 참조하십시오 . 기본적으로 &일반적으로 lvalue에만 바인딩하지만에 대한 예외가 rvalue있습니다. 대안이 있습니다.
Aaron McDaid

1
@AaronMcDaid 이것은 C ++ 11 이전에도 항상 알고 있어야한다는 의미에서 오래된 뉴스입니다. 그리고 그것에 관해서는 아무것도 바뀌지 않았습니다.
Luc Danton

71

거의 모든 경우에 의미는 다음 중 하나 여야합니다.

bar(foo f); // want to obtain a copy of f
bar(const foo& f); // want to read f
bar(foo& f); // want to modify f

다른 모든 서명은 드물게 사용하고 정당화해야합니다. 컴파일러는 이제 항상 가장 효율적인 방법으로이 작업을 수행합니다. 코드 작성을 계속할 수 있습니다!


2
인수를 수정하려면 포인터를 전달하는 것이 좋습니다. Google 스타일 가이드에 동의하면 함수의 서명 ( google-styleguide.googlecode.com/svn/trunk/… ) 을 다시 확인하지 않고도 인수가 수정된다는 것이 더 분명해 집니다.
Max Lybbert

40
포인터 전달을 싫어하는 이유는 함수에 가능한 실패 상태를 추가하기 때문입니다. 나는 그들이라도 유용 올바른 있도록이 크게에서 숨기기에 버그 공간을 감소시키기 때문에, 내 모든 함수를 작성하려고합니다. foo(bar& x) { x.a = 3; }보다 신뢰성 (읽을!) 많은의 지옥이다foo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;
Ayjay가

22
@Max Lybbert : 포인터 매개 변수를 사용하면 함수의 서명을 확인할 필요는 없지만 함수가 소유권을 가져 오는 경우 널 포인터를 전달할 수 있는지 문서를 확인해야합니다. IMHO, 포인터 매개 변수는 비 const 참조보다 훨씬 적은 정보를 전달합니다. 그러나 콜 사이트에서 인수가 수정 될 수 있다는 시각적 단서가 있으면 좋을 것입니다 ( refC # 의 키워드 와 같은 ).
Luc Touraille

가치를 지니고 이동 의미론에 의존하는 것과 관련하여이 세 가지 선택은 매개 변수의 의도 된 사용을 설명하는 데 더 효과적이라고 생각합니다. 이것들은 내가 항상 따르는 지침입니다.
Trevor Hickey

1
@AaronMcDaid is shared_ptr intended to never be null? Much as (I think) unique_ptr is?두 가지 가정이 모두 틀립니다. unique_ptrshared_ptr널 (null) / 저장할 수있는 nullptr값. null 값에 대해 걱정하지 않으려면 절대 null이 될 수 없으므로 참조를 사용해야합니다. 당신은 또한 ->당신이 성가신 것을 발견 타이핑 할 필요가 없습니다 :)
Julian

10

함수 본문 내에서 객체의 사본이 필요하거나 객체 만 이동해야하는 경우 매개 변수를 값으로 전달하십시오. const&객체에 대한 비 변동 액세스 만 필요한 경우 통과 하십시오.

객체 복사 예 :

void copy_antipattern(T const& t) { // (Don't do this.)
    auto copy = t;
    t.some_mutating_function();
}

void copy_pattern(T t) { // (Do this instead.)
    t.some_mutating_function();
}

객체 이동 예 :

std::vector<T> v; 

void move_antipattern(T const& t) {
    v.push_back(t); 
}

void move_pattern(T t) {
    v.push_back(std::move(t)); 
}

비 변동 액세스 예 :

void read_pattern(T const& t) {
    t.some_const_function();
}

이론적 근거는 Dave AbrahamsXiang Fan의 블로그 게시물을 참조하십시오 .


0

함수의 서명은 의도 된 용도를 반영해야합니다. 가독성은 옵티 마이저에도 중요합니다.

이것은 이론적으로 적어도 실제로는 아니더라도 몇 년 안에는 가장 빠른 코드를 생성하기위한 최적화의 전제 조건입니다.

매개 변수 전달과 관련하여 성능 고려 사항이 종종 과대 평가됩니다. 완벽한 전달이 예입니다. emplace_back어쨌든 같은 기능 은 대부분 매우 짧고 인라인됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.