728x90
728x90
복사생성자의 의미, 필요성 , 정의

 

5-1 C++ & C 스타일 초기화

 

두 가지 형태의 초기화 :: InitStyle.cpp, InitStyle2.cpp

C++ 에서는 아래로 묵시적변환이 되서 둘은 같은 것이다.

// C 스타일의 초기화 문장
int main(void)
{
	int val1=20;
	AAA al=10;
// C++ 스타일의 초기화 문장
int main(void)
{
	int val1(20);
	AAA a1(10);

 

 

 

5-2 복사 생성자의 형태

 

생성자가 오버로딩된 형태의 클래스 예제
class AAA
{
public : 
AAA(){
	cout<<"AAA() 호출"<<endl;
}
AAA(int i){
	cout<<"AAA(int i) 호출"<<endl;
}
AAA(const AAA& a){ //복사생성자
//생성자의 이름에 해당하는 클래스의 객체를 인자로 받을 수 있는
//AAA클래스 객체를 인자로 받을 수 있는
 
	cout<<"AAA(const AAA& a) 호출"<<endl;
}
};
int main(void)
{
	AAA obj1; //메모리공간 할당 후 void형 생성자 호출되면서 객체 생성 완료
	AAA obj2(10); 
	AAA obj3(obj2);
	// 3번 생성자 호출, 1번 복사생성자 호출
    
	return 0;
}

 

복사생성자 정의

복사를 하기 위한 용도로 사용될 수 있는 생성자

 

 

 

5-3 디폴트 복사 생성자

 

복사생성자의 의미와 기능에 대해 알아본다.

※ 배운 것 중 디폴트로 제공되는 것 : 생성자, 소멸자, 복사생성자
생성자소멸자는 순서 때문이나 복사생성자는 역할이 있다.

 

디폴트 복사 생성자

사용자 정의 복사 생성자가 없을 때 자동 삽입
멤버변수 대 멤버변수의 복사를 진행

#include <iostream>

using namespace std;

class Point
{
    int x, y;
public:
    Point(int _x, int _y){ 
        x=_x;
        y=_y;
    }
    Point(const Point& p){ // 복사생성자
        x=p.x; //p1 객체에 직접접근
        y=p.y;
    }
    void ShowData(){
        cout<<x<<' '<<y<<endl;
    }
};

int main()
{
    Point p1(10, 20);
    Point p2(p1);
    p1.ShowData();
    p2.ShowData();

    return 0;
}

컴파일러에 의해 자동 제공되어 에러가 없다.
쓰레기값도 들어가지 않아 멤버 대 멤버복사가 일어남을 알 수 있다.
복사생성자는 멤버변수의 갯수와 타입에 따라 정의되는 형태가 달라진다

#include <iostream>

using namespace std;

class Point
{
    int x, y;
public:
    Point(int _x, int _y){ 
        x=_x;
        y=_y;
    }
    // 생략해도 문제 없음
    // Point(const Point& p){ // 복사생성자
    //     x=p.x; //p1 객체에 직접접근
    //     y=p.y;
    // }
    void ShowData(){
        cout<<x<<' '<<y<<endl;
    }
};

int main()
{
    Point p1(10, 20);
    Point p2(p1);
    p1.ShowData();
    p2.ShowData();

    return 0;
}

쓰레기값이 출력되지 않습니당.

 

 

 

 

 

5-4 Deep Copy

 

디폴트 복사 생성자 복사 형태

얕은 복사 (Shallow Copy)

 

 

디폴트 복사 생성자의 문제점

얕은 복사에 의한 메모리 참조 오류

(좌)얕은복사. "KIM" 은 힙에 저장, (우)깊은복사

 

컴파일은 잘 되는데 런타임 에러가 발생한다.

#include <iostream>
#include <string.h>

using namespace std;

class Person
{
    char* name; //name이 멤버지, 포인터가 가리키는 문자열이 멤버가 아니다
    char* phone;
    int age;
    // Person 멤버는 두개의 포인터와 1개의 변수
public:
   Person(const char* _name, const char* _phone, int _age);
   
//   Person(const Person& p){
//       // 디폴트 복사생성자, 멤버 대 멤버 복사
        // 생략
//       name=p.name;
//       phone=p.phone;
//       age=p.age;
//   }

   ~Person();
   void ShowData();
};

Person::Person(const char* _name, const char* _phone, int _age)
{
    name=new char[strlen(_name)+1];
    strcpy(name, _name);
    
    phone=new char[strlen(_name)+1];
    strcpy(phone, _phone);
    
    age=_age;
}
Person::~Person(){
    delete []name;
    delete []phone;
}

void Person::ShowData()
{
    cout<<"name:"<<name<<endl;
    cout<<"phone:"<<phone<<endl;
    cout<<"age:"<<age<<endl;
}

int main()
{
    Person p1("KIM", "010-000-000", 22);
    Person p2=p1; //Person p2(p1); 묵시적 변환, 복사생성자 호출
    return 0;
}

 

 p1, p2는 name이 가리키는 값을 동일하게 가리킨다.

그러나 메인함수가 끝나는 순간 p2, p1 객체가 사라지고
(객체가 생성되는 순서랑 반대로 사라진다, 스택형태의 성질)
소멸자가 호출되어 "KIM", "010...이 사라진다.

소멸자에서는 문자열을 해제하는데 name 포인터가 가리키는 문자열을 두 번 소멸하려고 해서 문제가 생긴 것.
(이미 사라지고 없음)
하나의 메모리공간을 두 번 소멸하려 해서 문제
즉 p1이 완전히 복사된 게 아니다.

 

깊은 복사를 하는 복사생성자 예제 

런타임에러가 발생하지 않는다.
내용은 같지만 다 복사가 된 것.

#include <iostream>
#include <string.h>

using namespace std;

class Person
{
    char* name;  
    char* phone;
    int age;
    // Person 멤버는 두개의 포인터와 1개의 변수
public:
    Person(const char* _name, const char* _phone, int _age);
   
    // 깊은 복사를 하는 복사생성자
    Person(const Person& p){
        // 문자열 공간 할당
        name = new char[strlen(p.name)+1]; //동적할당을 하니 소멸자의 명시적 제공이 필요할 것
        strcpy(name, p.name);
        phone = new char[strlen(p.phone)+1];
        strcpy(phone, p.phone);
        age=p.age;
  }
   ~Person();
   void ShowData();
};

Person::Person(const char* _name, const char* _phone, int _age)
{
    name=new char[strlen(_name)+1];
    strcpy(name, _name);
    
    phone=new char[strlen(_name)+1];
    strcpy(phone, _phone);
    
    age=_age;
}
Person::~Person(){
    delete []name;
    delete []phone;
}

void Person::ShowData()
{
    cout<<"name:"<<name<<endl;
    cout<<"phone:"<<phone<<endl;
    cout<<"age:"<<age<<endl;
}

int main()
{
    Person p1("KIM", "010-000-000", 22);
    Person p2=p1; //Person p2(p1); 묵시적 변환, 복사생성자 호출
    p2.ShowData();
    return 0;
}

 

 

 

 

5-5 복사 생성자의 호출 시기

 

복사 생성자 호출 형태 3가지

case 1. 기존에 생성된 객체로 새로운 객체 초기화
case 2. 함수 호출 시 객체를 값에 의해 전달
case 3. 함수 내에서 객체를 값에 의해 리턴

 

...생성자 내에서 동적할당 시 복사생성자 정의가 필요하다고 배웠다.

Q. 클래스 내에서 동적할당 시 복사생성자 정의가 필요할까?
A. 프로그램 관점에서는 아니지만, 클래스 내에서 확장성 등의 이유로 복사생성자 정의가 필요하다.
프로그램이 아니라 클래스를 기준으로 복사생성자 여부를 결정짓게 된다.

 

 

case1. 기존에 생성된 객체로 새로운 객체 초기화
 #include <iostream>

class AAA
{
	int val;
public:
	AAA(int i){  //i에 10이 전달되어 val=10
	    val=j;
	}
	AAA(const AAA& a){
	    cout<<"AAA(const A &a) 호출"<<endl;
	    val=a.val;
	}
    void ShowData(){
        cout<<"val: "<<val<<endl;
    }
};
int main()
{
    AAA obj1(10);
    AAA obj2=obj1; //복사생성자 호출
    // 묵시적 변환됨 AAA obj2(obj1);
    // obj1기존의 객체, obj2새객체
    // 기존에 생성된 객체로 새로운 객체 초기화
    
    return 0;
}

 

 

Case2. 함수 호출 시객체를 값에 의해 전달

기본자료형 샘플

int fct(int a)
{
// 인자가 전달되면서 a가 초기화된다
// b값을 받기 위해서는 a라는 메모리공간 할당
// b값이 전달되면서 a가 10으로 초기화
}

int main(){
	int b=10;
    fct(b);
}

// 1. 메모리공간 a 할당
// 2. b값이 전달되면서 초기화 (a는 10)

객체

// 매개변수 a는 메모리공간 할당, 생성자가 호출되어서 객체이다.
int function(AAA a) // 참조가 아니라 값에 의해 전달.
{
	// a라는 이름으로 메모리공간 할당
	// 전달값으로 초기화
    // obj가 지닌 값 val이 다이렉트로 전달되어 30으로 채워지는 게 아님
    // 객체의 경우 값의 초기화는 obj객체가 a객체의 생성자의 매개변수로 전달된다.
    // a객체의 복사생성자를 호출하면서 obj가 전달되는 것.
	a.ShowData();
}
int main(){
	AAA obj(30);
    function(obj);
    return 0;
}
// 객체의 경우에는 a를 채우는 게 아니고
// 객체의 복사생성자를 호출하면서 복사하려는 대상 객체(obj)가 전달된다.

// 값의 대입은 복사생성자 호출을 통해 이루어짐...

 

 

case 3. 함수 내에서 객체를 값에 의해 리턴
AAA function(void)
{
	AAA a(10);
    return a;
    // a라는 이름으로 객체 생성
    // val은 10으로 초기화
    // 메인함수 영역으로 값에 의해 a객체 리턴
}
int main()
{
	function(); // a객체 생성
	function().ShowData(); // a객체의 복사본이 메인함수 영역으로 리턴
    // 클래스 객체의 경우 다이렉트로 집어넣는게 아니다.
    // a객체의 복사본도 객체이다
    // 1. a클래스 참조를 해서 a객체와 같은 형태로 메모리 공간 할당, 
    // 복사본 객체의 멤버변수 val은 쓰레기값으로 초기화된 상태.
    // 2. a 객체의 복사본 객체의 복사생성자를 호출하면서 복사의 대상인 a 객체를 인자로 전달한다
    // 복사생성자 내에서 멤버 대 멤버 복사가 이뤄짐
    // 3. a 객체 멤버변수 val의 값으로 복사본객체의 val 값이 초기화
    // 4. 메인함수로 리턴
	reuturn 0;
}
728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v