1] 복사 생성자?
1. C++ 스타일의 초기화
우리는 지금까지 밑의 방법으로 변수 및 참조자(reference)를 초기화해 왔다.

int num = 20;
int &ref = num;

하지만 C++ 에서는 다음의 방식으로 초기화가 가능하다. 

int num(20);
int &ref(num);

즉, int num이 int num(20)과 의미가 동일하다. 이것을 객체와 연관시켜보자. 

class SoSimple
{
private:
    int num1;
    int num2;
public:
    SoSimple(int n1, int n2) : num1(n1), num2(n2) { }
}
       
int main(void)
{
    SoSimple sim1(15, 20);
    SoSimple sim2 = sim1;

    return 0;
}

위 코드에서 sim2는 sim1에 멤버변수값을 깊은 복사하게 된다. 

이전에 살펴봤듯이, 

SoSimple sim2 = sim1
SoSimple Sim2(sim1)

위 두 문장은 동일한 의미로 해석된다. 
SoSimple Sim2(sim1) 코드가 정상적으로 작동하려면 SoSimple 객체를 인자로 받는 생성자가 있어야 되는것 아닌가? 그렇다! 이 생성자가 바로 복사 생성자이고, 이것은 컴파일러에 의해서 자동으로 생성된다. 
즉, 아래와 같은 복사 생성자가 자동으로 삽입된다.

SoSimple(const SoSimple& copy) : num1(copy.num1), num2(copy.num2) {  } 

2. explicit로 복사 생성자의 묵시적 호출을 막자!
SoSimple sim2 = sim1은 SoSimple sim2(sim1)로 컴파일러에 의해 변환되어 복사생성자가 호출된다. 즉, 프로그래머가 의도하지 않더라도 컴파일러에 의해 호출되는 것을 묵시적 호출이라 부른다. 
다만, 위와 같은 묵시적 호출이 마음에 들지 않으면 "explicit" 키워드를 사용해서 막을 수 있다.

explicit SoSimple(const SoSimple& copy) : num1(copy.num1), num2(copy.num2) {  } 

위와같이 선언하면, 복사생성자를 호출하기 위해서는 명시적으로 호출해야 한다. 
그리고 이와같은 묵시적 호출은 복사 생성자에게만 일어나는 것이 아니다. 전달인자가 하나인 생성자가 있다면, 이 역시 묵시적 변환이 발생한다. 다음 코드를 보자. 

class AAA
{    

private:       
    int num;
public:
    AAA(int n) : num(n) { }
}

위와 같이 정의된 클래스가 있으면,

AAA obj = 3; 은 AAA obj(3); 으로 변환된다. 이와같은 경우에 explicit 키워드를 써서 명시적 호출만 허용하면, AAA obj(3); 와 같은 방법으로 객체를 생성해야 한다. 

2] 복사 생성자의 호출 시점 
1.  메모리 공간의 할당과 초기화가 동시에 일어나는 상황
: 복사 생성자의 호출시기를 논하기에 앞서, 먼저 메모리 공간이 할당과 동시에 초기화되는 상황을 나열해 보자. 

int num1 = num2;

위 코드에서 num1 이라는 메모리 공간을 할당해서 num2의 값으로 초기화 시키고 있다. 

int SimpleFunc(int n)

    ...
    return n;
}

int num = 10;
cout<<SimpleFunc(num)<<endl; 

위 코드에서 SimpleFunc의 매개변수 int n은 SimpleFunc 호출 시점에 메모리 공간이 할당됨과 동시에 num 변수의 값인 10으로 초기화 된다. 

그리고! return n; 문장에서도 변수 n의 값을 return 하기위한 임시 메모리 공간이 할당됨과 동시에 n 변수의 값으로 초기화 된다.
?? 반환하는데 메모리 공간이 할당된다? 조금 이해가 안갈 수도 있다. 
하지만, cout<<SimpleFunc(num)<<endl; 문장이 실행되는 과정을 생각해보면 이해할 수 있을것이다.
SimpleFunc 함수에 의해서 반환되는 값을 메모리 공간의 어딘가에 저장해 놓지 않았다면, cout에 의해 출력이 가능하겠는가? 
값이 출력되기 위해서는 그 값을 참조할 수 있어야 하고, 참조가 가능 하려면 메모리 공간의 어딘가에 저장되어야 한다. 

객체의 경우도 마찬가지다. 

SoSimple SimpleFuncObj(SoSimple ob)
{
    ...
    return ob;
}
    
int main(void)
{
    SoSimple obj;
    SimpleFuncObj(obj);
    
    return 0;
}

위의 코드에서도 SimpleFuncOb 함수가 호출되는 순간, 매개변수 ob의 객체 생성과 동시에 초기화가 일어나고 return ob; 에 의해서도 임시 객체 생성과 동시에 초기화가 일어난다. 

2. 할당 이후, 복사 생성자를 통한 초기화
: 위에서 할당과 동시에 초기화가 되는 세가지 시점을 살펴보았다. 
- 객체 생성과 동시에 초기화
- 함수 전달시 매개 변수 객체 생성과 동시에 초기화
- 함수에서 return시 반환을 위한 임시 객체 생성과 동시에 초기화

위와 같은 상황에서 SoSimple 클래스 인스턴스에 대한 메모리가 생성되고 SoSimple 인스턴스를 매개 변수로 받아 복사생성자가 호출되면서 해당 인스턴스가 초기화 된다. 
즉,  SimpleFuncObj() 함수를 한번 호출하면 매개변수 ob의 복사생성자가 호출되고, return 문에 의한 임시 객체에 대한 메모리가 할당되고 그 임시 객체의 복사생성자가 호출된다. 

* 다만, SoSimple tempRef = SimpleFuncObj(obj) 호출시 추가적으로 tempRef 객체가 생성되고 tempRef에 대한 생성자 호출이 일어나지 않는다. SimpleFuncObj 함수의 return 문에 의한 임시객체가 생성되고 그 임시객체를 반환하지 않고 tempRef가 참조하게 만듦으로써, 객체의 생성 수를 하나 줄여준다. 

'C++' 카테고리의 다른 글

[C++] 상속  (0) 2017.12.25
[C++] Friend와 Static 그리고 Const  (0) 2017.12.25
[C++] 클래스의 완성  (1) 2017.12.23
[C++] 클래스의 기본  (0) 2017.12.23
[C++] C언어 기반의 C++(2)  (1) 2017.12.19
Posted by 홍성곤
,