C++, 복사 생성자, 대입 연산자.

wafe의 이미지

MyClass라는 클래스를 정의했습니다. 이 클래스는 기본 생성자, 복사 생성자와 대입 연산자를 정의 합니다.

My Class
{
  ...
  MyClass();  -> 기본 생성자
  MyClass(const MyClass&); -> 복사 생성자
  MyClass& operator=(const MyClass&); -> 대입 연산자
  ..
};

그리고 이 클래스를 값으로 리턴하는 함수가 있습니다.

MyClass func1()
{
  MyClass ret;
  ...
  return ret;
}

이렇게 정의해 두고, 기본 생성자, 복사 생성자, 대입 연산자에 출력문을 두고 다음과 같이 테스트 해 보았습니다.

MyClass a = func1();

이 경우에는 기본 생성자, 복사 생성자 순서로 호출됩니다. 그런데 다음과 같이 사용하면,
MyClass b;
b = func1();

기본 생성자, 기본 생성자, 복사 생성자, 대입 연산자 순서로 호출됩니다.

여기서 두 번째의 경우에 func1() 안에서 ret를 선언할 때 기본 생성자가 호출되었고, return 할 때 임시 개체를 생성하여 ret를 복사하면서 복사 생성자가 호출되고, 임시 개체를 b에 대입할 때 대입 연산자가 호출되는 것으로 생각할 수 있습니다.

제 생각에는 중간에 임시 개체를 생성하는 것은 불필요할 것 같은데, 왜 임시 개체를 사용하는 것일까요? 반환값을 바로 b 에다가 바로 대입해 주는게 더 효율적이지 않나요?

winner의 이미지

기본적으로 자동지역객체는 함수가 종료되는 순간 사라져야 합니다.

이 의미를 그대로 따른다면 ret 는 함수를 호출한 쪽에서 사용할 수 없습니다.
ret 를 그대로 대입문에서 사용하고자 한다면 ret 의 소멸은 함수를 호출한 쪽에서 작업을 끝낼 때까지 지연되어야 합니다.
그렇게 하는 한가지 방법은 소멸자를 함수내에서 실행시키는 것이 아니라 함수호출한 쪽에서 하게 하는 방법입니다.

실제로 최적화에 대한 issue 중에 반환객체에 대한 이야기가 있는 것으로 압니다.
최적화정도에 따라 wafe 씨의 code 는 정확히 어떻게 객체들이 생성되고, 소멸될지는 알 수 없습니다.

잘은 모르겠습니다만 몇가지 calling convention 중 이런 최적화를 지원하는 방법이 있는 것으로 압니다.

wafe의 이미지

음... 그렇다면 C++에서는 개체가 언제 생성되고 소멸될지 정확히 예측할 수는 없다는 것이군요.

아무튼, 제가 보여드린 코드에서 첫 번째 방법과 두 번째 방법의 동작 방식에 차이가 있는 것이 신기합니다.

제가 보여드린 첫 번째 예제

MyClass b = func1();

의 실행결과에서는 복사 생성자만 불립니다. 즉, 임시 개체가 생성되지 않고, func1()에서 선언된 ret를 b의 복사 생성자에 직접 전달해 준다고 생각할 수 있습니다. 그런데 두 번째 코드의 실행결과에서는 임시 개체가 생성되는 것으로 보인다고 앞에서도 말씀 드렸습니다.

첫 번째 예제에서 ret를 b의 복사 생성자에서 사용한다면, 함수가 종료된 뒤에도 자동지역개체가 남아있다는 것이므로 말이 되지 않습니다. 그렇다면 임시 개체를 만들어서 ret를 복사하고, 그냥 그 녀석을 b로 사용해버리는 것일까요? 이것도 그다지 신통한 설명은 아닌듯하네요.

Heejoon Lee

cupper의 이미지

C++에서 객체가 언제 생성되고, 소멸되는지 알수 없다는 것은 말이 안되는것 같습니다.
저의 짧은 생각으로 올리신 코드의 차이점은 '초기화'와 '대입'의 차이가 있는것 같습니다.
처음의 코드는 초기화 코드로 객체는 func()내에서 생성이 되고 이것이 카피가 되어 객체가 생성이 된 예이고, 두번째 예는 객체를 하나 생성한 후에 다시 함수를 호출하여 그 함수에서 생성된 객체를 카피하여 먼저 만든 객체에 값을 카피하여 할당하여 준거 같습니다.

즉, 말씀하신 임시객체라는 것은 복사생성자에 의해서 이루어 지는 일련의 작업(?)이라고 생각이 됩니다.

두서없이 쓰게 되었군요... ^^*

winner의 이미지

저도 착각을 했습니다만 wafe 씨의 글을 찬찬히 읽으니 초기와와 대입의 차이를 모르시는 것 같지는 않습니다.

cupper wrote:
C++에서 객체가 언제 생성되고, 소멸되는지 알수 없다는 것은 말이 안되는것 같습니다.

아시는지 모르겠습니다만 static 이나 전역에서 선언되는 정적객체는 알 수 없습니다.
솔직히 저는 대충만 이해하고 있습니다만... -_-

첫번째 예제는 wafe 씨가 마지막에 적으신 것과 같은 개념으로 최적화되었다고 봅니다.
calling convention 에 의한 최적화는 아닌 것으로 보이네요.

위의 제글 wrote:
최적화정도에 따라 wafe 씨의 code 는 정확히 어떻게 객체들이 생성되고, 소멸될지는 알 수 없습니다.

이부분은 임시객체가 생성될지는 알 수 없다로 바꾸어야 할 것 같습니다. calling convention 의 변형은 예외... -_-
dudungsil의 이미지

C에서 파라미터와 리턴값은 call by value를 이용합니다. 이를 계승한 C++도 똑같은 방법을 이용합니다.

즉 리턴값의 복사본을 만들고 그 복사본을 넘겨 준다는겁니다. 두번째 예로 적으신 코드를 보겠습니다.

MyClass b; // 여기서 기본 생성자가 호출됩니다.
b = func1();

func1()을 자세히 보죠.

MyClass func1()
{
MyClass ret; // 여기서 또 기본 생성자
...
return ret; // 값을 리턴합니다. 복사본을 만들죠. 그래서 복사 생성자
}

그리고 이걸
b = func1 () ; // 즉 b에 대입합니다.

그래서 기본,기본,복사,대입 생성자가 실행이 됩니다. 여기서 예측이 안된다거나 문제가 되는게 어딘지요? calling convention에 의한 최적화라는게 뭔지도 모르겠구요.

ps. 사실 가장 큰 문제는 위의 func1 ()에 있습니다. 함수에서 클래스를 생성하고 그걸 리턴하는 구조도 이해를 못하겠군요. 저라면

class MyClass
{
public:
    MyClass& func1 ()
    {
        ....
        return *this ;
    }
} ;

와 같은 방법이나 func1 ()을 friend로 만들고 MyClass를 포인터나 참조로
넘겨주는 방법을 사용했을것 같네요.

산넘어 산

wafe의 이미지

아 저런 코드를 쓴 것은 복사 생성자와 대입 연산자가 언제 불리는지 시험해 보기 위한 것이었습니다. 제가 무엇을 궁금해 하는 것인지 설명이 부족했던것 같습니다.

MyClass a = func1();

에서는 복사 생성자가 한번만 불립니다(임시 반환개체 없이 a만 생성된다고 볼 수 있음. 혹은 임시 반환개체가 그대로 a로 바뀐다고 볼 수 있음).

MyClass b;
b = func1();

에서는 복사 생성자가 불리고 대입도 합니다(임시로 반환 개체가 생성되고 이것을 b에 대입한다고 생각할 수 있음).

즉, 첫 번째 코드와 두 번째 코드에서 일어나는 값에 의한 반환의 실제 행동이 다르게 보인다는 것입니다. 두 번째 코드에서 일어나는 일로 미루어 짐작컨대, 첫 번째 코드에서는 복사 생성자가 두 번 불리는 것이 정상이라고 생각됩니다. 즉,
MyClass a = func1();
이라는 코드에서는 func1()의 ret를 임시 반환 개체에 복사할 때 한 번, 임시 반환 개체를 a에 복사할 때 한 번 불릴 것이라고 예상할 수 있다는 것이지요. 그런데 실제로는 그렇지 않고 복사 생성자는 한 번만 호출됩니다. 이것이 가능하다면,
MyClass b;
b = func1();
이라는 코드에서도 "기본, 기본, 복사, 대입"의 순서가 아니라 "기본, 기본, 대입"만으로 실행되는 것이 가능할 것이라고 생각되고, 후자가 더 효율적이라고 생각됩니다.

지금으로서는 최적화 과정에서 일어나는 차이(임시 반환 개체가 생성되고 되지 않는 차이)라고 보는게 가장 설득력있네요. :)

Heejoon Lee

akbar의 이미지

...

pynoos의 이미지

wafe 님은 반환시에 일어나는 객체의 생성/소멸관계에 관심이 있으신것만 봐도 본격적인 고민을 시작하셨다고 봐도 과언이 아닙니다. :)

생성될 때 대입되는 꼴로 만들어질때는 func1안에서 기본생성자가 그리고 대입되는 꼴에서는 복사생성자가 불리는 것이 쉽게 이해가 갑니다.

두번째 예에서는

Quote:
기본 생성자, 기본 생성자, 복사 생성자, 대입 연산자

func1에서의 기본생성자 대입전 기본생성자 까지는 두개가 이해가 됩니다만, 복사 생성자 없이 바로 함수 안에 있는 녀석이 대입될 수 없지 않겠느냐입니다.

문제는 그 대입연산자가 인자로 받는 (대개 const & 겠지요) 가시 범위가 func1까지 미치지 못한다는 데 있습니다. 따라서 한번의 복사생성이 일어나서 대입연산자의 인자로 넘길 수 있는 가시범위내에 복사생성되어야만 합니다.

이렇게 생각하면, 왜 처음 예에서는 복사생성 두번일어나지 않느냐고 반문할 수 있는데, 그 임시 생성이 필요한 곳이 다른 객체의 생성을 위한 복사생성일 경우에는 최적화되어 한번으로 멈추게 됩니다.

어렵습니다... :(

cdpark의 이미지

MyClass func1() 
{ 
  MyClass ret; 
  ... 
  return ret; 
}

대신에

MyClass func1() 
{ 
  int values; 
  ... 
  return Mycalss(values, ...); 
} 

식으로 코드를 짜면 기본생성자를 부르는 걸 막을 수 있습니다.

예를 들어 complex 숫자 둘을 더하는 연산의 경우

MyComplex add(complex &a, complex &b)
{
   MyComplex ret(a.real+b.real, a.imag+b.imag);

   return ret;
}

식보다
MyComplex add(complex &a, complex &b)
{
   return MyComplex(a.real+b.real, a.imag+b.imag);
}

식으로 부르는 것처럼요.
winner의 이미지

백문이 불여일견이라고 한번 code 를 작성해보아습니다.
그런데 저는 임시객체를 생성하지 않는군요.
그냥 대입연산 한번합니다.

제가 test 한 환경은 Fedora Core 1 - kernel 2.6.0-1, GCC 3.3.2 입니다.

dudungsil의 이미지

MyClass a = func1 () ;
은 초기화와 물려 있기 때문입니다.

class OtherClass
{
public:
    OtherClass (MyClass&) ;
private:
    MyClass m_class ;
}

1.
OtherClass::OtherClass (MyClass& rhs)
{
    m_class = rhs ;
}

2.
OtherClass::OtherClass (MyClass& rhs)
: m_class (rhs)
{}

를 비교해보셔도 같은 결과가 나오죠. 초기화와 이미 생성되어 있는 개체는 다르게 처리됩니다. 이부분에 대한 정확한 스펙을 보지 않아서 장담할수는 없지만 초기화와 대입에 관련된 고전적 문제라고 알고 있습니다. 다른문제와는 관련이 없어 보이는데요.

ps. 위에 코드를 적고 보니 최초에 제기된 문제의 코드와 다를바가 없군요 -_-

산넘어 산

creib2000의 이미지

RVO(Return Value Optimizaition)와 연관된 것 같군요.

MyClass func1()
{
MyClass ret;
...
return ret;
}

MyClass func1()
{
...
return MyClass(); <--- 이름없는 객체 생성
}
는 확실히 다른겁니다...
이름 있는 객체의 반환과 이름 없는 객체의 반환에서 이름없는쪽은
RVO가 수행되어 임시객체 생성이 따로 필요 없다고 하는군요.
( 사실 객체가 하나 밖에 없으니... ㅡ_ㅡ);

MyClass c = func1();
했더니 기본생성자,대입연산자 이렇게 호출되네요.

틀린곳 있으면 다른분이 꼭 알려주시길...
MEC++ 항목20에 자세히 나와있군요... :wink:

winner의 이미지

MyClass func1()
{
    MyClass ret;
    ...
    return ret;
}

MyClass func1()
{
    ...
    return MyClass(); <--- 이름없는 객체 생성
}

모두 대입연산만 수행합니다.

creib2000 wrote:

MyClass c = func1();
했더니 기본생성자 한번만 호출됩니다...

그리고 기본생성자와 복사생성자를 단어선택(개념은 아시는 것 같은데...)을 실수하신 것 같네요.

위의 code 는 MyClass c( func1() ); 과 동일합니다.

cdpark wrote:

코드:
MyClass func1()
{
MyClass ret;
...
return ret;
}

대신에

코드:
MyClass func1()
{
int values;
...
return Mycalss(values, ...);
}

식으로 코드를 짜면 기본생성자를 부르는 걸 막을 수 있습니다.

위의 내용은 반환객체에 대한 내용이 아니라 func1() 내에서 객체생성을 할 때 기본생성자를 사용하지 않기 위한 내용입니까?

sandro의 이미지

위의 소스 코드를 한번 분석해 보죠

먼저 func1 은 다음과 같겠죠

MyClass func1() 
{ 
  MyClass ret;   // ret의 생성자인 MyClass() 호출
  ... 

  return ret;  
  // 이부분에서 return type에 따라 MyClass의 임시 객체(편의상 tmp라 부르겠음) 가 생성 하면서
  // tmp의 생성자인 MyClass() 호출
  // 그 다음 tmp = ret 가 수행 되면서 대입 연산자가 호출
}

제가 알고 있는게 맞다면 위와 같이 수행됩니다.

그럼 다음 코드를 분석해 보죠.
MyClass a = func1();
여기서 MyClass의 생성자는 explicit로 선언이 안되 있습니다.
그러므로 위의 코드는 다음과 같이 바뀝니다.
MyClass a(func1());
그러면 위의 func1이 불린후 a의 생성자 MyClass(const MyClass&);가 호출됩니다. 여기서 &가 붙었으므로 복사 생성자는 불리지 않습니다.

다음 코드를 보죠

MyClass b;   // 여기서 b의 MyClass() 호출
b = func1(); // func1 호출후 생긴 임시 객체가 b에 할당 되므로 대입 연산자 호출

제가 알고 있는게 맞으면 위와 같이 호출이 수행 됩니다.

아 그런데 실제 머신 코드가 생성될때는 최적화 기능들 때문에 다른 상황이 발생 할수 있습니다. 그러나 제대로 한다면 위와 같이 되야 합니다.

無心

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.