문제

C ++의 참고 문헌이 저를 당황스럽게합니다. :)

기본 아이디어는 함수에서 객체를 반환하려고한다는 것입니다. 포인터를 반환하지 않고하고 싶어요 (그러면 수동으로해야하기 때문입니다. delete 그것), 그리고 가능하면 (효율성을 위해, 자연스럽게 추가 : 또한 사본 생성자를 쓰지 않는지 궁금하기 때문입니다).

대체로, 여기에 내가 찾은 옵션이 있습니다.

  • 함수 리턴 유형은 클래스 자체 일 수 있습니다 (MyClass fun() { ... }) 또는 수업에 대한 참조 (MyClass& fun() { ... }).
  • 함수는 리턴 라인에서 변수를 구성 할 수 있습니다 (return MyClass(a,b,c);) 또는 기존 변수를 반환합니다 (MyClass x(a,b,c); return x;).
  • 변수를 수신하는 코드는 다음 유형의 변수를 가질 수 있습니다.MyClass x = fun(); 또는 MyClass& x = fun();)
  • 변수를 수신하는 코드는 즉시 새 변수를 생성 할 수 있습니다 (MyClass x = fun();) 또는 기존 변수에 할당합니다 (MyClass x; x = fun();)

그리고 그것에 대한 몇 가지 생각 :

  • 리턴 유형을 갖는 것은 나쁜 생각 인 것 같습니다. MyClass& 그것은 항상 변수가 반환되기 전에 파괴되기 때문입니다.
  • 카피 생성자는 기존 변수를 반환 할 때만 관여하는 것 같습니다. 리턴 라인에 구성된 변수를 반환 할 때는 결코 호출되지 않습니다.
  • 결과를 기존 변수에 할당하면 값이 반환되기 전에 소멸자는 항상 시작됩니다. 또한 사본 생성자가 호출되지 않지만 Target Variable은 함수에서 반환 된 객체의 멤버 값을 수신합니다.

이 결과는 너무 일관성이 없어서 완전히 혼란스러워합니다. 그렇다면 여기서 정확히 무슨 일이 일어나고 있습니까? 함수에서 객체를 올바르게 구성하고 반환해야합니까?

도움이 되었습니까?

해결책

C ++에서 복사를 이해하는 가장 좋은 방법은 종종 인공 예제 및 기기를 생성하려고 시도하지 않는 것입니다. 컴파일러는 적합한 것처럼 보이는 것처럼 복사 생성자 호출을 제거하고 추가 할 수 있습니다.

결론 - 값을 반환 해야하는 경우 값을 반환하고 "비용"에 대해 걱정하지 마십시오.

다른 팁

권장 독서 : 효과적인 C ++ Scott Meyers. 당신은 거기 에서이 주제에 대한 아주 좋은 설명을 찾습니다.

간단히 말해서, 값으로 반환하면 카피 생성기와 소멸자는 기본적으로 참여합니다 (컴파일러가 최적화하지 않는 한 - 일부 경우에 발생하는 일입니다).

참조 (또는 포인터)로 로컬 (스택에 구성된 변수)으로 반환하는 경우, 반환시 객체가 파괴되어서 문제를 초대하므로 결과적으로 매달려있는 참조가 있습니다.

함수에서 객체를 구성하고 반환하는 정식 방법은 다음과 같은 값에 의한 것입니다.

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

이것을 사용하는 경우 소유권 문제, 끊임없이 참조 등에 대해 걱정할 필요가 없으며 컴파일러는 추가 사본 생성자 / 소멸자 호출을 최적화 할 가능성이 높으므로 성능에 대해 걱정할 필요가 없습니다.

참조로 반환 할 수 있습니다. new (즉, 힙에) -이 물체는 함수에서 돌아올 때 파괴되지 않습니다. 그러나 나중에 전화를 통해 어딘가에 명시 적으로 파괴해야합니다. delete.

또한 기술적으로 값으로 반환 된 객체를 참조로 저장하는 것도 가능합니다.

MyClass& x = fun();

그러나 Afaik이 작업을 수행하는 데는 별다른 점이 없습니다. 특히 현재 범위를 벗어난 프로그램의 다른 부분에 대한이 참조를 쉽게 전달할 수 있기 때문입니다. 그러나 대상이 참조되었습니다 x 현재 범위를 떠나 자마자 파괴 될 로컬 물체입니다. 따라서이 스타일은 불쾌한 버그로 이어질 수 있습니다.

~에 대해 읽다 RVO 그리고 NRVO (한 단어 로이 두 사람은 반환 가치 최적화와 RVO라는 이름을 지정하며 컴파일러가 달성하려는 일을 수행하기 위해 사용하는 최적화 기술입니다).

StackoverFlow에서 많은 주제를 찾을 수 있습니다

다음과 같은 객체를 만드는 경우 :

MyClass foo(a, b, c);

그런 다음 기능 프레임의 스택에 있습니다. 그 함수가 끝나면 프레임이 스택에서 튀어 나오고 해당 프레임의 모든 물체가 파괴됩니다. 이것을 피할 방법이 없습니다.

따라서 객체를 발신자에게 반환하려면 옵션 만 다음과 같습니다.

  • 값별로 반환 - 사본 생성자가 필요합니다 (그러나 사본 생성자에 대한 호출은 최적화 될 수 있음).
  • 포인터를 반환하고 스마트 포인터를 사용하여 처리 할 때 직접 삭제하십시오.

로컬 객체를 구성한 다음 해당 로컬 메모리에 대한 참조를 호출 컨텍스트로 반환하려고 시도하는 것은 일관성이 없습니다. 호출 스코프는 호출 스코프에 로컬에있는 메모리에 액세스 할 수 없습니다. 해당 지역 메모리는 기능을 소유 한 함수의 지속 시간에만 유효하며 다른 방법으로 실행은 해당 범위에 남아 있습니다. C ++의 프로그램에 대해 이해해야합니다.

참조를 반환하는 것이 합리적 인 유일한 시간은 기존 객체에 대한 참조를 반환하는 경우입니다. 명백한 예를 들어, 거의 모든 ioStream 멤버 함수는 ioStream에 대한 참조를 반환합니다. ioStream 자체는 멤버 기능이 호출되기 전에 존재하며 호출 후에도 계속 존재합니다.

표준은 "복사 elision"을 허용하므로 객체를 반환 할 때 사본 생성자를 호출 할 필요가 없습니다. 이것은 NRVO (Name Return Value Optimization)와 익명 반환 값 최적화 (일반적으로 RVO)의 두 가지 형태로 제공됩니다.

당신이 말하는 바에 따르면, 당신의 컴파일러는 RVO를 구현하지만 nrvo는 아닙니다. 아마 다소 오래된 컴파일러. 대부분의 현재 컴파일러는 두 가지를 모두 구현합니다. 이 경우 일치하지 않은 DTOR는 아마도 GCC 3.4 또는 그에 대한 주변과 같은 것일 수 있습니다. 버전을 기억하지는 않지만 주변에 버그가 있었던 버그가있었습니다. 물론, 계측기가 옳지 않을 수도 있으므로 악기를 사용하지 않은 CTOR가 사용되고 일치하는 DTOR가 해당 객체에 대해 호출되고 있습니다.

결국, 당신은 하나의 간단한 사실에 붙어 있습니다. 물체를 반환해야한다면 물체를 반환해야합니다. 특히, 참조는 기존 객체의 (수정 된 버전)에만 액세스 할 수 있지만 그 객체는 어느 시점에서도 구성되어야했습니다. 문제를 일으키지 않고 기존 객체를 수정할 수 있다면 괜찮습니다. 새로운 객체가 필요하고 이미 가지고있는 것과 분리되어 있다면, 그 일을 계속하십시오. 물체를 사전 만들고 참조를 전달하면 반품 자체를 더 빨리 만들 수 있지만 전체적으로 시간을 절약하지는 않습니다. 객체 생성은 함수 내부 또는 외부에서 수행하든 거의 동일한 비용을 가지고 있습니다. 합리적으로 현대적인 컴파일러는 RVO가 포함되므로 함수에서이를 생성하는 데 추가 비용을 지불하지 않아도됩니다. 함수가 반환 된 후에도 여전히 액세스 할 수있는 "제자리에"를 구성하십시오.

기본적으로, 참조를 반환하는 것은 메소드를 떠난 후에도 객체가 여전히 존재하는 경우에만 의미가 있습니다. 컴파일러는 파괴되는 것에 대한 언급을 반환하면 경고합니다.

값으로 객체보다는 참조를 반환하면 객체를 복사 할 수 있습니다.

참고 문헌은 교화가 다르기 때문에 포인터보다 안전하지만, 무대 뒤에서 포인터입니다.

사용 사례에 따라 한 가지 잠재적 솔루션은 기능 외부의 객체를 기본 구성하는 것입니다. 안에 그것에 대한 참조, 다음과 같이 함수 내에서 참조 된 객체를 초기화합니다.

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

이제 이것은 기본 구성을 위해 불가능한 경우 (또는 의미가없는 경우) 작동하지 않습니다. Foo 객체를 한 다음 나중에 초기화하십시오. 이 경우 카피 건설을 피하는 유일한 실제 옵션은 포인터를 힙합 할당 물체로 반환하는 것입니다.

그러나 왜 당신이 처음에 카피 건설을 피하려고 노력하는지 생각해보십시오. 사본 구성의 "비용"이 프로그램에 실제로 영향을 미치는가, 아니면 조기 최적화의 경우입니까?

당신은 다음 중 하나에 붙어 있습니다.

1) 포인터 반환

myclass* func () {// 일부 stuf return new myclass (a, b, c); }

2) 객체의 사본을 반환 myclass func () {return myclass (a, b, c); }

함수가 클래스의 구성원이고 참조가 클래스의 구성원 인 변수에서 나온 경우를 제외하고는 Func Scope를 종료 한 후 객체가 파괴되기 때문에 참조를 반환하는 것은 유효하지 않습니다.

직접적인 답변이 아니라 실행 가능한 제안 : Auto_ptr 또는 Smart_ptr에 포장 된 포인터를 반환 할 수도 있습니다. 그러면 당신은 어떤 생성자와 소멸자가 전화를 걸고 언제인지 통제 할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top