복사생성자가 호출되지 않는 이유가 뭔가요?

dltkddyd의 이미지

Time 클래스에 AddTime 멤버함수가 다음과 같이 선언돼 있습니다.

const Time AddTime(const Time& T) const;

그리고 본문 코드에 다음과 같이 진술문은 언급했습니다.

Time C=A.AddTime(B);

여기서 C에 반환값이 대입되는 순간에 복사생성자가 호출돼야할 것으로 생각했는데, 복사생성자가 호출되지 않습니다. 물론 복사생성자는 클래스에 정의해놨고요. 복사생성자가 호출돼야하는거 아닌가요?
부연하자면 AddTime이 반환하는 값은 Time형의 지역객체입니다.

kukyakya의 이미지

RVO 또는 NRVO를 검색해보세요.

dltkddyd의 이미지

rvo라는 것은 반환되는 함수의 반환객체에 대한 소멸 책임은 호출원에 있다는 것이죠? 저는 이렇게 이해했습니다. 그런데 선언과 동시에 +연산자를 호출할 때와 선언 후에 +연산자를 호출해서 바로 대입연산을 할 때 조금 어긋나는 것 같습니다.

#include <iostream>
using namespace std;
#include <cstring>
 
class Test {
public:
	char* str;
	Test() {
cout<<"Test()"<<endl;
		str=NULL;
	}
	Test(const char* _str) {
cout<<"Test(const char* _str)"<<endl;
		str=new char[strlen(_str)+1];
		strcpy(str,_str);
	}
	Test(const Test& src) {
cout<<"Test(Test& src)"<<endl;
		str=new char[strlen(str)+1];
		strcpy( str,src.str ); 
	}
	~Test() {
cout<<"~Test()"<<endl;
		delete[] str;
		str=NULL;
	}
	Test operator+(Test& src) {
		Test temp;//1
		temp.str=new char[strlen(str)+strlen(src.str)+1];
		strcpy(temp.str,str);
		strcpy(&temp.str[strlen(str)],src.str);
		return temp;		
	}
	Test operator=(const Test& right) {
cout<<"="<<endl;
		delete[] str;
		str=NULL;
		str=new char[strlen(right.str)+1];
		strcpy(str,right.str);
		return (*this);
	}
 
 
};
 
int main() {
	Test obj1("delicate");
	cout<<obj1.str<<endl;
 
	Test obj2("machine");
	cout<<obj2.str<<endl;
 
	cout<<"begin"<<endl;
	//Test obj3=obj1+obj2;//1) 그러니까 이 부분의 주석 부분과 
	cout<<"end"<<endl;
 
	Test obj3;
	obj3=obj1+obj2;//2) 이 부분이요.
	cout<<obj3.str<<endl;
 
 
 	return 0;
}

1)에서는 복사생성자가 호출되지 않습니다.
그런데
2)에서는 복사생성자가 호출됩니다.

반환되는 지역객체를 선언과 동시에 대입받을 때와 반환되는 지역객체를 대입연산자를 통해 대입받을 때에 복사생성자 호출여부가 다른데요. 안 되려면 둘 다 복사생성자가 호출되어서는 안되죠?

이렇게 이해해야 할까요?
선언과 동시에 함수의 객체를 반환받을 때에는 그 소멸의 책임을 선언변수가 떠않는다. 그러나 그렇지 않을 경우에는 호출원의 임시객체가 생성되어 그 임시객체가 소멸의 책임을 떠않는다.
즉 '문제는 임시객체가 생성되느냐 안되느냐의 문제이다.'라고 이해하면 될까요? 틀린 부분이 있다면 어디가 틀렸을까요?

원래는 복사대입연산자 저런 식으로 반환을 언급하지 않지만 비교상 저렇게 언급해본겁니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

kukyakya의 이미지

지금 포인트를 완전히 잘못 잡으셨습니다. 소멸 책임이고 뭐고는 이 문제와 전혀 상관이 없습니다.

질문글들 보면 열의도 있으시고 열심히 하시려는 것도 같아보이는데, 모래바닥 위에 모래성을 쌓으시는 것 같아보입니다.

lowlevel 쪽에 관심이 있으시다면 진심으로 '컴퓨터 구조 개론' 정도의 키워드로 원론적인 내용을 다시 한번 공부하셨으면 좋겠습니다.

간단하게 설명하자면 '객체를 생성한 후, 함수 내부에서 생성하여 리턴한 객체를 복사'하는 과정이 필요한데, 이러면 불필요한 데이터 복사 과정이 생기기 때문에 컴파일러가 이를 최적화해주는 것입니다.

1은 복사 생성자가 호출되는 것이고, 2는 대입 연산자가 호출되는 것입니다. 원래의 질문과는 전혀 상관 없는 주제입니다.

dltkddyd의 이미지

2에서도 복사생성자가 호출됩니다. 단 그 복사생성자는 +연산자가 아니라 대입연산자=이 값을 반환하는 과정에서 발생하는 것을 확인했습니다. 그러나까 호출원쪽에 해당되는 객체가 다시 반환될 때에는 복사생성자가 호출되는군요.
1에 대해서는 지금 검색해서 찾아보고 있는 중입니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

gilgil의 이미지

Test obj3=obj1+obj2; //1)
이 부분은
Test obj3(obj1+obj2); //1)
와 동일합니다.

dltkddyd의 이미지

obj1+obj2 에서 반환된 지역객체가 다시 복사생성자로 전달이 될테고, 그러면 복사생성자의 출력문이 출력되어야 합니다. 그런데 출력문이 출력되지 않습니다. 해당 출력문이 출력되지 않는다는 것은 복사생성자가 호출되지 않은 것으로 보이는데요. 쿠캬캬님은 복사생성자가 호출되었다고 하시기에 어째서 그런가를 지금 탐구하고 있습니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

gilgil의 이미지


최적화가 없다면

Test obj3;
obj3=obj1+obj2; // 이건 assign operator

Test obj3=obj1+obj2; // 이건 copy constructor

가 호출되어 진다고 보는 게 맞습니다.

그런데 RVO 정책에 의해서 1)번 코드는

1. obj1+obj2아 이루어 지고(+ operator)
2. 그 결과가 obj3에 들어 가게 되는데(copy constructor)

디버깅을 해 보면 + operator의 temp 객체의 포인터와 obj3의 포인터 값이 같습니다.
즉, 이는 RVO가 적용되었다는 뜻이고, 당연히 copy constructor가 호출되지 않을 수 있겠네요.

제 생각에는 컴파일러마다 다를 거 같습니다(확실하지는 않음).

http://en.wikipedia.org/wiki/Return_value_optimization

익명 사용자의 이미지

그러니까 아래에 링크 걸어주신 글을 해석해봤습니다. 답변 글로 달려고 했는데 아래로 글이 올라가버리네요. 그러니가 RVO라는 것이 생성자를 호출하지 않고도 정상적으로 객체가 형성되도록 하는 건가보네요. 생성자를 호출하는 것이 물론 C++의 표준 사항이겠으나 컴파일러의 최적화를 위해서 생성자 호출을 하지 않고 구현된 내부의(전 그게 무엇인지 모르지만) 더 빠른 방식을 선택해서 컴파일하는 기법. 이런 식으로 받아들이면 될까요?

익명 사용자의 이미지

생성자를 호출하는 것이 정석이지만 컴파일러 최적화를 위해 함수가 지역객체 반환시 또는 선언시 함수의 반환값을 대입받을 시에 생성자를 호출하지 않고 객체를 생성하는 구현 방식을 RVO라고 한다라고 이해했습니다. 올바르게 이해한 것인지 모르겠네요.

gilgil의 이미지

저도 어설프게 알고 있었는데 이번 기회에 확실히 알게 되었네요.
감사합니다. ^^

dltkddyd의 이미지

제가 감사드립니다. 좋은 링크 걸어주셔서 모르는 것을 알게됐습니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

	Test(const Test& src) {
cout&lt;<"Test(Test& src)"&lt;<endl;
		str=new char[strlen(str)+1];//이 부분을
                   str=new char[strlen(src.str)+1];//이렇게 언급했어야 하는 것이었네요
		strcpy( str,src.str ); 
	}

저 부분을 고치면 포인터가 이상작동하는 경우가 발생하지 않습니다. 죄송합니다. 제가 생성자를 잘못 작성했네요.

그리고 링크 걸어주신 내용 해석해봤습니다.

일반적으로 C++ 표준은 컴파일러가 어떠한 최적화라도 수행하도록 한다. 그리고 마치 표준요구사항들이 이행된 양 결과적으로 실행가능한 전시성의 준수할 만한 동작들을 제공한다. 이를 통상 "as-if rule"이라고 한다. RVO는 C++ 표준에서 as-if rule보다 진보된 특별한 구문을 언급한다. 비록 복사생성자가 부수적인 효과를 수반한다할지라도, 어떤 구현은 반환진술문으로부터 초래되는 복사연산을 제거할지도 모른다.
다음 예제는 비록 복사 생성자가 가시적인 부수 효과를 수반한다 할지라도 그 구현이 하나 또는 두 개의 복제물들을 제거할지도 모르는 시나리오를 강조한다. 제거될지도 모르는 첫 번째 복제물은 C()가 함수 f의 반환 값으로 복사되는 것이다. 두 번째 복제물은 f에 의해 반환된 임시 객체의 복제물이 obj에 이르는 것이다.

#include <iostream>
struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};
 
C f() {
  return C();
}
 
int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

컴파일러와 컴파일러의 설정에 따라서 그 프로그램의 결과는 다음 출력의 어떠한 것이라도 출력하것이다.

Hello World!
A copy was made.
A copy was made.

Hello Wordl!
A copy was made.

Hello World!

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.