C++ Chapter 9.10 : 복사 생성자, 복사 생략, 리턴값 최적화
카테고리: Cpp
태그: Cpp Programming
인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 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 객체이다.
- 메모리 낭비를 줄이기 위해 참조로 가져 온다.
- const
- 멤버 변수 값들을 전부 복사해와 초기화 한다.
복사 생성자가 호출될 때
#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_copy2
와Fraction(3, 10)
는 주소 동일.
Fraction(3, 10)
메모리는fr_copy2
가 되었으므로 사라지지 않는다.- 익명 객체는 원래 바로 사라지지만 자신(메모리)를 참조해주는 변수가 생겼으므로 사라지지 않음
- 즉 복사 과정 없이 그냥
- 컴파일러는 불필요하게 생성자가 많이 호출되는 것을 방지하기 위해 복사 생성을 수행하지 않고
-
- 인수로 넘겨진 Fraction(3, 10)은 익명 객체다.
이처럼 복사할 대상이 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
값은 다르다.- 주소가 다르다. 즉 별개의 메모리다.
temp
가result
에 복사되었다.temp
는 임시 객체이므로 복사된 후 사라진다.
- 복사 과정이 생략되지 않는다.
- 릴리즈 모드
- 최적화를 위해 컴파일러가 복사 과정을 생략한다.
- 복사 생성자가 호출되지 않는다.
&temp
와&result
값은 동일하다.- 주소가 같다. 즉 동일한 메모리다.
temp
가result
에 복사되지 않고result
가 곧temp
메모리가 되었다.
- 최적화를 위해 컴파일러가 복사 과정을 생략한다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글남기기