배너
닫기
배너

C++에서 복사생성자 다루기

  • 등록 2015.09.09 14:12:34
URL복사
[무료 웨비나] 미래의 자동차 조명을 재조명하다: Analog Devices의 혁신적인 솔루션 (5/29)

1. 복사생성자(Copy Constructor)의 개념

C, C++은 하드웨어 제어 등 가장 많이 쓰는 언어 중 하나이다. 이번에는 C++에서 복사생성자에 대해 파악해 본다. C++에서는 C에는 없는 복사생성자(Copy Constructor)가 사용된다. 같은 class에서 객체를 생성할 때 새로 멤버 데이터를 일일이 지정하는 번거로움을 피하기 위해 이미 존재하는 객체의 정보를 그대로 가져다 사용한다.

또한 동적메모리에서 두 개 이상의 객체를 다룰 때 한 객체의 메모리가 삭제되면 다른 객체의 메모리도 삭제되는 것을 막기 위해서이다. 복사생성자는 같은 class에서 한 객체의 값을 다른 객체에 전해 주어야 할 때 다른 생성자처럼 객체 생성을 도와주면서 객체의 데이터를 초기화 시킨다.

한 객체의 값을 다른 객체에 전해 주는 방법에는 2가지가 있다. 첫째, 대입을 통해서다. 둘째는 초기화를 통해서다. 복사생성자는 초기화 방법을 통해서 한 객체를 값을 다른 객체에 전해주는 것이다. 왜 초기화를 하는가 하면 프로그램이 원하지 않는 쓰레기 값 대신 의미 있는 값으로 동작시키려고 하는 것이다. 복사생성자는 사용자가 직접 정의할 수도 있고, 사용자가 별도로 정의하지 않으면 컴파일러가 자동으로 생성하고, 호출한다.
요약하면 복사생성자는 같은 class 타입을 갖는 객체를 하나의 매개변수로 전달받은 후 값을 복사하여 다른 객체를 초기화하는 생성자로서 다른 class 타입에서는 사용할 수 없다.

 

2. 복사생성자 형식

class명 (const class명 & 객체명)
{
 //
}

 

여기서 const는 constant(상수)의 약자로서 객체를 상수로 만들어 주는 역할을 한다. 또한 함수의 전달인자로 넘어온 객체를 변경하지 못하도록 보호장치의 역할도 한다.

 

<예제 1>
Bird::Bird(const Bird & beauty) // 복사생성자 형식 정의
{
 Name = new char[30]; //포인터 멤버 변수 Name에 동적메모리 영역 할당
 strcpy(Name, beauty.Name); // 문자열을 새 위치에 복사
 Age = beauty.Age;  // Age 멤버변수 초기화
 Weight = beauty.Weight; // Weight 멤버변수 초기화
}

 

3. 복사생성자를 사용하는 방법

가. class명 객체명(객체);
Man obj2(obj1); // class Man에서 객체 obj1을 인자로 하는 객체 obj2를 생성한다.

나. class명 객체명 = 객체;
Man obj2 = obj1; // class Man에서 객체 obj1을 대입해서 객체 obj2를 생성한다.

다. class명 객체명 = 생성자(객체);
Man obj2 = Man(obj1); // 생성자 Man의 obj1객체를 이용하여 객체 obj2를 생성

 

4. 복사생성자 예제

다음의 예제 2는 Woman이라는 class를 정의하고 이름, 성별, 키, 몸무게를 입력한다. 이 프로그램은 생성자에서 메모리 공간을 동적 할당하고 있으며, 소멸자가 이를 해제하고 있다. 그래서 복사생성자가 필요하며 이 때는 깊은 복사를 한다.

 

<예제 2>
  #include<iostream>
  using namespace std;
 
  class Woman {
   char* name;
   char* sex;
   int stature;
   int weight;   
 
  public:
   Woman();
   ~Woman();
   Woman(const Woman& Search);
   Woman(char* _name, char* _sex, int _stature, int _weight);
   void ShowData();   
  };
 
  Woman::Woman() {}
 
  Woman::~Woman()  //소멸자 정의
  {
   delete []name;
   delete []sex;
  }
 
  Woman::Woman(const Woman& Search)  // 복사생성자 정의
  {
   name=new char[strlen(Search.name)+1]; // 이름의 길이를 참조해서 같은 길이의 메모리 공간을 동적으로 할당
   strcpy(name, Search.name); // 이름 복사
 
   sex=new char[strlen(Search.sex)+1]; // 성별의 길이를 참조해서 같은 길이의 메모리 공간을 동적으로 할당
   strcpy(sex, Search.sex); // 성별을 복사
 
   stature=Search.stature; // 멤버변수 stature의 값을 복사
   weight=Search.weight; //멤버변수 weight의 값을 복사
  }
 
  Woman::Woman(char* _name, char* _sex, int _stature, int _weight)
  {
   name=new char[strlen(_name)+1];
   strcpy(name, _name);
     
   sex=new char[strlen(_sex)+1];
   strcpy(sex, _sex);
     
   stature=_stature;
   weight=_weight;
  }
 
  void Woman::ShowData()
  {
   cout<<"이  름 : "<<name<<endl;
   cout<<"성  별 : "<<sex<<endl;
   cout<<"키     : "<<stature<<endl;
   cout<<"몸무게 : "<<weight<<endl;
  }
 
  int main(void)
  {
   Woman body("이혜숙", "여", 165, 55);
   Woman body2(body);
   body2.ShowData();
 
   return 0;
  }

 

 

 
<그림1> 예제 2의 실행 결과


5. 얕은 복사(shallow copy)

사용자가 복사생성자를 코딩하지 않았을 때 만일 컴파일러가 복사생성자를 사용할 필요가 있다면 컴파일러는 기존 객체의 모든 멤버들을 멤버 대 멤버의 형태로 각각 복사해서 새 객체를 초기화하는 디폴트 복사생성자를 생성한다. 이 디폴트 복사생성자가 작동하는 방식을 얕은 복사라고 한다. 그런데 얕은 복사를 하면 객체들 간에 간섭이 일어나서 신뢰할 수 없는 메모리로 인해 문제를 일으킬 수 있다. 다음의 (예제 3) 프로그램을 살펴본다.

 

<예제 3>
#include <iostream>
using namespace std;

 

class Go
{
 char* str;

 

public:
 Go();
 Go(char* _str);
 void Set(char* _str);
 void Showdata();
};
 
Go::Go()
{
 str=NULL;
}
Go::Go(char* _str)
{
 str=new char[strlen(_str)+1];
 strcpy(str, _str);
}
void Go::Set(char* _str)

 strcpy(str, _str);
}
void Go::Showdata()
{
 cout<<str<<endl;
}

int main(void)
{
 Go A("Go");
 Go B=A;

 

 A.Set("COME");
 A.Showdata();
 B.Showdata();
 return 0;
}

 

 

 
<그림2> 예제 3의 실행 결과

 

위의 실행 결과에서 A객체는 A.Set(“COME”);로 인해 COME 값을 갖고, B객체는 값이 바뀌어 “GO”라는 값이 될 것으로 예상됐으나, 생각한 값과 다르게 그냥 “COME” 값이 되었다. 왜냐하면 생성한 객체는 두 개인데 호출된 생성자는 하나였기 때문이다.

 

6. 깊은 복사(deep copy)

사용자가 직접 정의하는 복사생성자를 깊은 복사라고 한다. 사용자가 복사생성자를 추가하고 메모리를 따로 할당하여 복사가 일어나도 메모리를 공유하지 않게 하는 것이다. 이렇게 하면 객체들 간에 간섭이 일어나지 않는다. 위의 (예제 3) 프로그램에서 class Go의 public:에 Go(const Go & _Go);를 넣고 Go::Go(const Go & _Go)라는 복사생성자를 만들어서 함수 Go::Go() 뒤에 넣고 다시 실행시켜 보자.

 

public:
 Go();
 Go(const Go & _Go);
 Go(char* _str);
 void Set(char* _str);
 void Showdata();
};

 

다음과 같이 복사 생성자를 만들어서 넣는다.
Go::Go(const Go & _Go)
{
 str=new char[strlen(_Go.str)+1];
 strcpy(str, _Go.str);
}

 

 

 
<그림3> 예제 3을 수정한 실행 결과


그러면 정상적으로 A객체 값은 “COME”, B객체 값은 “Go” 로 실행되었다. 이것은 바로 복사생성자를 추가함으로써 메모리를 따로 할당하여 복사를 해도 메모리를 공유하지 않게 하였기 때문이다.

 

7. 분석과 고찰

얕은 복사(shallow copy)는 암시적인 복사생성자로서 컴파일러가 자동으로 생성해 주기 때문에 별도로 정의할 필요는 없다. 그러나 얕은 복사는 포인터변수를 멤버로 갖는 클래스에서 완전한 복사가 수행되지 않음으로써 그 멤버와 연결된 동적할당 기억공간이 있을 때 문제가 발생할 수 있다. 따라서 프로그램 작성 시 동적할당에 문제가 발생할 수 있다고 판단될 때에는 깊은 복사(deep copy)를 사용하여 사용자가 직접 복사생성자를 작성한다. 복사생성자를 사용자가 직접 작성하면 얕은 복사 기능은 사라진다.


 

<윤덕하 객원전문기자 _ 아이에셋>










배너









주요파트너/추천기업