13.12 The copy constructor

주홍영·2022년 3월 18일
0

Learncpp.com

목록 보기
155/199

https://www.learncpp.com/cpp-tutorial/the-copy-constructor/

Recapping the types of initialization

우리는 다음 몇몇의 레슨에서 initialization에 대해서 많은 이야기를 나눌 예정이므로
먼저 c++에서 지원하는 initialization에 대해서 요약해보는 시간을 가지려고 한다
direct init, uniform init, copy init 등이 있다

예시 코드로 살펴보자

#include <cassert>
#include <iostream>

class Fraction
{
private:
    int m_numerator{};
    int m_denominator{};

public:
    // Default constructor
    Fraction(int numerator=0, int denominator=1)
        : m_numerator{numerator}, m_denominator{denominator}
    {
        assert(denominator != 0);
    }

    friend std::ostream& operator<<(std::ostream& out, const Fraction& f1);
};

std::ostream& operator<<(std::ostream& out, const Fraction& f1)
{
	out << f1.m_numerator << '/' << f1.m_denominator;
	return out;
}

우리는 다음과 같이 direct init을 할 수 있다

int x(5); // Direct initialize an integer
Fraction fiveThirds(5, 3); // Direct initialize a Fraction, calls Fraction(int, int) constructor

c++11에서는 uniform init을 할 수 있다

int x { 5 }; // Uniform initialization of an integer
Fraction fiveThirds {5, 3}; // Uniform initialization of a Fraction, calls Fraction(int, int) constructor

마지막으로 copy init도 할 수 있다

int x = 6; // Copy initialize an integer
Fraction six = Fraction(6); // Copy initialize a Fraction, will call Fraction(6, 1)
Fraction seven = 7; // Copy initialize a Fraction.  The compiler will try to find a way to convert 7 to a Fraction, which will invoke the Fraction(7, 1) constructor.

The copy constructor

#include <cassert>
#include <iostream>

class Fraction
{
private:
    int m_numerator{};
    int m_denominator{};

public:
    // Default constructor
    Fraction(int numerator=0, int denominator=1)
        : m_numerator{numerator}, m_denominator{denominator}
    {
        assert(denominator != 0);
    }

    friend std::ostream& operator<<(std::ostream& out, const Fraction& f1);
};

std::ostream& operator<<(std::ostream& out, const Fraction& f1)
{
	out << f1.m_numerator << '/' << f1.m_denominator;
	return out;
}

int main()
{
	Fraction fiveThirds { 5, 3 }; // Direct initialize a Fraction, calls Fraction(int, int) constructor
	Fraction fCopy { fiveThirds }; // Direct initialize -- with what constructor?
	std::cout << fCopy << '\n';
}

위 코드에서 fiveThird는 일반적인 direct init인 것을 알 수 있다
우리가 클래스에서 정의한 Fraction(int, int) constructor를 호출할 것이다

그렇다면 fCopy의 경우는 무엇일까?
우리는 이 사례에 주목해야 한다
fCopy의 경우에 argument로 똑같은 Fraction 타입의 fiveThirds object를 전달하고 있다

이를 copy constructor라고 한다. 이미 존재하는 object를 이용해 constructor를 작동하는 경우이다. 일반적으로 c++은 우리가 copy constructor를 만들지 않으면 default 버젼으로 만든다

만약 우리의 needs와 맞는다면 default copy constructor를 써도 크게 문제가 되지 않는다

#include <cassert>
#include <iostream>

class Fraction
{
private:
    int m_numerator{};
    int m_denominator{};

public:
    // Default constructor
    Fraction(int numerator=0, int denominator=1)
        : m_numerator{numerator}, m_denominator{denominator}
    {
        assert(denominator != 0);
    }

    // Copy constructor
    Fraction(const Fraction& fraction)
        : m_numerator{fraction.m_numerator}, m_denominator{fraction.m_denominator}
        // Note: We can access the members of parameter fraction directly, because we're inside the Fraction class
    {
        // no need to check for a denominator of 0 here since fraction must already be a valid Fraction
        std::cout << "Copy constructor called\n"; // just to prove it works
    }

    friend std::ostream& operator<<(std::ostream& out, const Fraction& f1);
};

std::ostream& operator<<(std::ostream& out, const Fraction& f1)
{
	out << f1.m_numerator << '/' << f1.m_denominator;
	return out;
}

int main()
{
	Fraction fiveThirds { 5, 3 }; // Direct initialize a Fraction, calls Fraction(int, int) constructor
	Fraction fCopy { fiveThirds }; // Direct initialize -- with Fraction copy constructor
	std::cout << fCopy << '\n';
}

위의 코드는 copy constructor의 예시이다
의도한 대로 구현을 진행하면 된다
여기서 주목할 점은 copy constructor에서 같은 객체가 아니지만 fraction의 private member variable에 접근이 가능하다는 것이다. 즉, 같은 타입인 경우에는 같은 object가 아니더라도 서로의 private member variable에 접근할 수 있다는 것을 알 수 있다

Preventing copies

만약 copy constructor를 private으로 설정한다면 컴파일 에러가 발생한다
하지만 현실적으로 그럴 이유가 없다

The copy constructor may be elided

#include <cassert>
#include <iostream>

class Fraction
{
private:
	int m_numerator{};
	int m_denominator{};

public:
    // Default constructor
    Fraction(int numerator=0, int denominator=1)
        : m_numerator{numerator}, m_denominator{denominator}
    {
        assert(denominator != 0);
    }

        // Copy constructor
	Fraction(const Fraction &fraction)
		: m_numerator{fraction.m_numerator}, m_denominator{fraction.m_denominator}
	{
		// no need to check for a denominator of 0 here since fraction must already be a valid Fraction
		std::cout << "Copy constructor called\n"; // just to prove it works
	}

	friend std::ostream& operator<<(std::ostream& out, const Fraction& f1);
};

std::ostream& operator<<(std::ostream& out, const Fraction& f1)
{
	out << f1.m_numerator << '/' << f1.m_denominator;
	return out;
}

int main()
{
	Fraction fiveThirds { Fraction { 5, 3 } };
	std::cout << fiveThirds;
	return 0;
}

main 함수에서 fiveThirds를 anonymous Fraction object로 initialization하고 있는 것을 알 수 있다. 그렇다면 우리는 copy constructor가 실행될 것이라고 예상되나 실상은
c++에서 이를 생략하고 다음과 같이 바꿔버린다

Fraction fiveThirds{ 5, 3 };

이를 elision이라고 부른다

profile
청룡동거주민

0개의 댓글