C++ Chapter 9.10 : 복사 생성자, 복사 생략, 리턴값 최적화

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!


chapter 9. 연산자 오버로딩 : 복사 생성자, 복사 생략, 리턴값 최적화

🔔 복사 생성자

복사 생성자는 이 포스트에서 한번 만났었다!

복사 생성자 : 어떤 객체를 복사하여 똑같은 타입의 객체를 생성할 때 복사 생성자가 호출된다.


복사 생성자 만들기

Fraction(const Fraction &fraction)  // copy constructor
	:m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
{ 
	cout << "Copy constructor called" << endl; 
}
  • 복사 생성자는 복사할 대상을 인수로 받는다.
    • const
      • 그저 복사만 할 것이라 복사할 대상으로 들어온 객체의 멤버 값을 변경 안할 것.
    • 복사할 대상은 같은 타입의 Fraction 객체이다.
    • 메모리 낭비를 줄이기 위해 참조로 가져 온다.
  • 멤버 변수 값들을 전부 복사해와 초기화 한다.


복사 생성자가 호출될 때

#include <iostream>
#include <cassert>
using namespace std;

class Fraction
{
private:
	int m_numerator;  // 분자
	int m_denominator; // 분모

public:
	Fraction(int num = 0, int den = 1)
		: m_numerator(num), m_denominator(den)
	{
		assert(den != 0);  // 분모가 0이면 안됨
        cout << "Constructor" << endl; 
	}
	
	Fraction(const Fraction &fraction)  // ✨ 복사 생성자
		:m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
	{ 
		cout << "Copy constructor" << endl; 
	}

	friend std::ostream & operator << (std::ostream & out, const Fraction & f)
	{
		out << f.m_numerator << "/" << f.m_denominator << endl;
		return out;
	}
};

int main()
{
	Fraction frac(3, 5);

	Fraction fr_copy(frac);  // 👈👈 복사생성자가 호출 된다!
    Fraction fr_copy2 = frac;   // 👈👈 복사생성자가 호출 된다!

	cout << frac << " " << fr_copy << " " << fr_copy2 << endl;  // 3/5 3/5 3/5 출력
	return 0;
}
  • Fraction fr_copy(frac);
    • 객체 fr_copy는 자기 자신과 같은 타입인 frac객체를 인수로 받아 생성되는 객체이다.
    • 자신을 생성하고 frac객체의 멤버 값들을 자신의 멤버에 복사한다.
    • 복사 생성자가 호출된다.
  • Fraction fr_copy2 = frac;
    • 이렇게 복사 초기화로 대입할 때도 복사 생성자가 호출된다.
    • 자신(fr_copy2 객체)을 생성하고 frac객체의 멤버 값들을 자신의 멤버에 복사한다.


복사 과정이 생략될 때

Fraction fr_copy2(Fraction(3, 10));
💎출력💎
Constructor
  • Fraction fr_copy2(Fraction(3, 10));
    • 인수로 넘겨진 Fraction(3, 10)익명 객체다.
      • R-value로서 임시적으로 잠깐 자리를 차지했다가 사라지는.
    • 직관적으로 생각해보면 Fraction(3, 10)이 생성되면서 Constructor가 출력되고 이 익명 객체를 fr_copy2에 복사하면서 Copy Constructor도 호출되야 하는데
      • 즉 이 한번의 실행에 생성자, 복사생성자가 둘 다 호출되야 하는데
    • 복사 생성자는 호출되지 않고 Fraction(3, 10) 생성시 호출된 일반 생성자만 호출된다.
      • 복사 과정이 생략 되었기 때문이다.

        • 컴파일러는 불필요하게 생성자가 많이 호출되는 것을 방지하기 위해 복사 생성을 수행하지 않고 fr_copy2 자체를 Fraction(3, 10)로 만들어 버린다.
          • 즉 복사 과정 없이 그냥 fr_copy2가 곧 Fraction(3, 10) 메모리를 참조하게 된다.
            • fr_copy2Fraction(3, 10)는 주소 동일.
          • Fraction(3, 10) 메모리는 fr_copy2가 되었으므로 사라지지 않는다.
            • 익명 객체는 원래 바로 사라지지만 자신(메모리)를 참조해주는 변수가 생겼으므로 사라지지 않음
        • image

이처럼 복사할 대상이 R-value 객체일 때 컴파일러가 복사 과정을 생략하기도 한다.

  • 복사 대상이 리턴받은 객체일 때, 위의 예시처럼 익명 객체일 때 등등
  • 복사 과정을 생략할지는 컴파일러가 판단한다. (항상 생략하는 것은 아니라고 한다.)
    • C++ 17 부터는 함수 내부에서 객체를 만들어 리턴하는 경우 등등 일부 경우에 대해서는 반드시 복사를 생략한다고 한다.


복사 생성자 호출을 막고 싶을 때

복사 생성자를 private영역에 정의 해버리면 호출되지 않는다.

private:
    Fraction(const Fraction &fraction)  // copy constructor
	:m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
    { 
	    cout << "Copy constructor called" << endl; 
    }

...

Fraction f(frac);  // 복사 생성자 호출 안됨
Fraction f = frac; // 복사 생성자 호출 안됨


🔔 리턴값 최적화

“복사 과정이 생략될 때” 문단과 연관된다.

임시 객체를 함수로 리턴받고 이를 복사 대상으로 삼을 때

  • 디버그 모드 👉 복사를 하며 복사 생성자가 호출된다.
  • 릴리즈 모드 👉 복사를 생략하며 복사 생성자가 호출되지 않는다.
    • 최적화를 위하여!
Fraction doSomething()
{
    Fraction temp(1, 2);
    cout << &temp << endl;

    return temp;
}

int main()
{
    Fraction result = doSomething();  // 👈👈👈
    cout << &result << endl;
}
  • doSomething() 함수는 임시 객체 temp를 리턴하는데 이는 지역 범위이므로 리턴 후 사라진다.
  • 디버그 모드
    • 복사 과정이 생략되지 않는다.
      • 복사 생성자가 호출된다.
    • &temp&result 값은 다르다.
      • 주소가 다르다. 즉 별개의 메모리다.
      • tempresult에 복사되었다.
        • temp는 임시 객체이므로 복사된 후 사라진다.
  • 릴리즈 모드
    • 최적화를 위해 컴파일러가 복사 과정을 생략한다.
      • 복사 생성자가 호출되지 않는다.
    • &temp&result 값은 동일하다.
      • 주소가 같다. 즉 동일한 메모리다.
      • tempresult에 복사되지 않고 result가 곧 temp메모리가 되었다.


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기


댓글남기기