복사 생성자
복사 생성자란 무엇일까?
Java에는 없는데 C++에는 있는 기능이다.
그럼 C에는 있을까? 없다.
OOP의 개념이 있어야 생성자가 있다.
생성자라는 개념이 있으려면 OOP가 있어야 한다.
그럼 Java는 OOP인데 왜 복사 생성자가 없냐?
클론 함수 ( = clone()) 이런 걸 사용해서 쓴다.
그럼 복사 생성자는 어떻게 만들까?
자신과 같은 클래스에 있는 개체를 매개변수로 받는 코드를 짜면 된다.
즉, 매개변수가 다른 개체 그것도
나랑 똑같은 클래스에 속한 개체면 복사 생성자이다.
// Vector.h
class Vector
{
public:
Vector(const Vector& other);
private:
int mX;
int mY;
};
// Vector.cpp
Vector::Vector(const Vector& other) : mX(other.mX), mY(other.mY)
{
}
초기화 목록에 other의 값을 대입하는 것만으로도 충분하다.
여기서 한 가지, other은 다른 개체이다.
하지만 같은 클래스이기 때문에 private 멤버에 접근할 수 있다.
복사 생성자
- (같은 클래스에 속한)다른 개체를 이용하여 새로운 개체를 초기화
- 같은 크기, 같은 데이터
<class-name> (const <class-name>&);
Vector(const Vector& other)
Vector a; // 매개변수 없는 생성자 호출
Vector b(a) //복사 생성자 호출
C++ 프로그래머가 툴의 도움 없이
곧바로 종이 위에 복사 생성자를 못쓰면 숙련이 안 됐다는 증거이다.
충분히 안 짜봤다는 증거이며, 기술에 대한 이해가 없는 것이다.
이것은 분명히 종이 위에 단번에 쓸 정도로 알아야 한다.
암시적 복사 생성자
전 포스팅에 기본 생성자가 없으면
컴파일러가 기본 생성자를 만들어 준다고 했었다.
복사 생성자도 이와 같다.
다음과 같이 생성자 2개를 만들어준다
(소멸자도 사용자가 만들어주지 않았다면
컴파일러가 자동으로 만들어준다.
이 외에도 복사 대입 연산자 또한 자동으로 만들어준다.)
암시적 복사 생성자는 얕은 복사를 수행한다.
- 멤버 별 복사
- 각 멤버의 값을 복사한다.
- 개체인 멤버 변수는 그 개체의 복사 생성자가 호출된다.
무슨 얘기냐?
각 멤버를 복사하는데 각 멤버의 값을 복사한다.
그 이상을 복사하지 않는다는 뜻이다.
Vector(const Vector& other) : mX(other.mX), mY(other.mY)
{};
멤버 변수가 개체라고 하면,
mX가 객체이고 mY가 개체이면,
위 코드의 other.mX는 또 다른 개체이다.
위의 내용만 보더라도 실제 복사 생성자가 호출되는 것을 알 수 있다.
그러나 얕은 복사에는 언제나 문제가 있다.
클래스에 포인터 형 변수가 있다면,
int가 아니라 point라면,
// 컴파일러가 암시적 복사 생성자를 자동으로 만들어 줌
// ClassRecord.h
class ClassRecord
{
public:
ClassRecord(const int* scores, int count);
~ClassRecord();
private:
int mCount;
int* mScores;
};
그럼 int* mScores와 같이
포인터 변수는 복사 생성자에서 값을 제대로 들고 있을지 모른다는 것이다.
이를 위해 얕은 복사를 알아보자.
얕은 복사에서는 개체는 단순히 원래 개체의 모든 변수의 데이터를 복사하여 생성한다.
개체의 변수가 메모리 힙 섹션에 정의되어 있지 않은 경우 이 방법이 효과적이지만,
일부 변수가 힙 섹션에서 동적으로 메모리에 할당된 경우
복사된 객체 변수도 동일한 메모리 위치를 참조한다.
이렇게 하면 모호성과 런타임 오류, dangling pointer가 생성된다.
두 객체 모두 동일한 메모리 위치를 참조하므로
한 객체에서 변경한 내용은 다른 객체에도 반영이 된다.
객체의 복제본을 만들고 싶었기 때문에 얕은 복사로는 이 목적을 충족할 수 없다.
3차원 공간에서 차원을 나타내는 Box라는 object가 있다고 가정하여,
정수 데이터 멤버를 갖는 개체 B1, B2를 고려해보자.
이제 멤버 중 하나가 정수 변수에 대한 포인터라고 가정해 보겠다.
여기서 얕은 복사는 포인터에 저장된 주소만 복사하여
동일한 개체를 가리키는 개체 멤버를 모두 선언한다.
이렇게 되면 B1에서 delete 한 뒤 nullptr을 해주게 되면
B2는 아직 그 12라는 값을 가진 포인터를 쓰고 싶은데,
갑자기 값이 사라지는 것이다.
// 접근 방식을 위한 C++ 프로그램
#include <iostream>
using namespace std;
// Box Class
class Box {
private:
int length;
int breadth;
int height;
int* p;
public:
// 얕은 복사 생성자
Box(const Box& other) : length (other.length), breadth(other.breadth), height(other.height), p(other.p)
{
}
// 깊은 복사 생성자
Box(const Box& other) : length(other.length), breadth(other.breadth), height(other.height)
{
p = new int; // 새로운 메모리 할당
*p = *(other.p); // 값 복사
}
// 치수 범위를 설정하는 함수
void SetDimensions(int length1, int breadth1, int height1, int x)
{
length = length1;
breadth = breadth1;
height = height1;
p = new int;
*p = x;
}
// Box 객체의 치수 크기를 표시하는 함수
void ShowDate()
{
cout << " Length = " << length
<< "\n Breadth = " << breadth
<< "\n Height = " << height
<< "\n P int pointing to = " << p
<< endl;
}
};
그럼 다시 돌아와서
ClassRecord 클래스로 깊은 복사를 구현하려면 어떻게 해야할까?
// ClassRecord.cpp
ClassRecord::ClassRecord(const int* scores, int count) : mCount(count)
{
// 여기서 굳이 우리가 메모리를 잡아주어
// 해당 메모리가 들어온 다음에 scores 기록하고
// 호출한 애가 지워버릴 수 있으니 우리만의 기록저장 공간을 확보하자는 의미이다.
mScores = new int[mCount];
memcpy(mScores, scores, mCount * sizeof(int));
}
포인터는 얕은 복사
개체 생성은 깊은 복사
기억하자~
그럼 컴파일러가 암시적 생성한 복사 생성자는?
얕은 복사하니
얕은 복사의 문제의 소지가 있는 클래스는?
포인터에 대해서는 깊은 복사 생성자로 만들어주자!
포인터는 얕은 복사
// ClassRecord.h
class ClassRecord
{
public:
ClassRecord(const int* scores, int count);
~ClassRecord();
private:
int mCount;
int* mScores;
};
// 암시적 생성 얕은 복사
ClassRecord::ClassRecord(const ClassRecord& other)
: mCount(other.mCount),
mScores(other.mScores)
{
}
// main.cpp
ClassRecord classRecord(scores, 5);
ClassRecord* classRecordCopy = new classRecord(classRecord);
실질적으로는,
이렇게 포인팅 하게 되는 것이다.
하지만 이 코드가 추가되면 다음과 같은 일이 벌어진다.
delete classRecord;
아까 가리키고 있던 내용을 지워버리면,
classRecord의 내용은 다 사라져있다.
그러면 이상한 곳을 가리키던 포인터를 쓰게되면 위험하다.
얕은 복사의 문제
자바에서 나오는 문제이기도 하다.
a라는 개체를 b가 참조하고 있고
a라는 개체가 어 나 이제 퇴근할게 하면
b는 아직 퇴근을 못해서
a가 가지고 있던 데이터를 자기 데이터니까 쓰려고 하면,
이상한 값을 건들이게 되는 것이다.
좀 더 유식하게 말해보자.
즉, 클래스 안에서 동적으로 메모리를 할당하고 있다면
얕은 복사가 가지는 위험성인 댕글링 포인터가 생길 문제의 소지가 있습니다.
따라서 직접 포인터에 있는 내용까지 복사해오는 깊은 복사를 해온다.
// ClassRecord.cpp
// 만들어줘야 할 깊은 복사
ClassRecord::ClassRecord(const int* scores, int count) : mCount(count)
{
mScores = new int[mCount];
memcpy(mScores, scores, mCount * sizeof(int));
}
그러면 원본 개체가 안전해진다.
즉, 내부에서 메모리를 할당하고 있다면
복사 생성자를 개발자가 생성해주는 것이 좋다.
- 예제 전체 코드 -
#include <memory>
// Vector.h
class Vector
{
public:
Vector(const Vector& other);
private:
int mX;
int mY;
};
// Vector.obj
class Vector
{
public:
Vector() {};
Vector(const Vector& other) : mX(other.mX), mY(other.mY)
{};
~Vector() {};
private:
int mX;
int mY;
};
// ClassRecord.h
class ClassRecord
{
public:
ClassRecord(const int* scores, int count);
ClassRecord(const ClassRecord& other);
~ClassRecord();
private:
int mCount;
int* mScores;
};
// ClassRecord.cpp
// 만들어줘야 할 깊은 복사
ClassRecord::ClassRecord(const int* scores, int count) : mCount(count)
{
mScores = new int[mCount];
memcpy(mScores, scores, mCount * sizeof(int));
}
// 암시적 생성 얕은 복사
ClassRecord::ClassRecord(const ClassRecord& other)
: mCount(other.mCount),
mScores(other.mScores)
{
}
// main.cpp
ClassRecord classRecord(scores, 5);
ClassRecord* classRecordCopy = new classRecord(classRecord);
// 접근 방식을 위한 C++ 프로그램
#include <iostream>
using namespace std;
// Box Class
class Box {
private:
int length;
int breadth;
int height;
int* p;
public:
// 얕은 복사 생성자
Box(const Box& other) : length (other.length), breadth(other.breadth), height(other.height), p(other.p)
{
}
// 깊은 복사 생성자
Box(const Box& other) : length(other.length), breadth(other.breadth), height(other.height)
{
p = new int; // 새로운 메모리 할당
*p = *(other.p); // 값 복사
}
// 치수 범위를 설정하는 함수
void SetDimensions(int length1, int breadth1, int height1, int x)
{
length = length1;
breadth = breadth1;
height = height1;
p = new int;
*p = x;
}
// Box 객체의 치수 크기를 표시하는 함수
void ShowDate()
{
cout << " Length = " << length
<< "\n Breadth = " << breadth
<< "\n Height = " << height
<< "\n P int pointing to = " << p
<< endl;
}
};
https://www.geeksforgeeks.org/shallow-copy-and-deep-copy-in-c/
'프로그래밍 언어 > C & C++ 정리' 카테고리의 다른 글
연산자 오버로딩과 const (0) | 2024.11.06 |
---|---|
C++ 함수 오버로딩과 C언어의 _Generic (4) | 2024.11.03 |
구조체와 클래스의 차이 (1) | 2024.11.02 |
new와 malloc의 차이 (1) | 2024.11.01 |
Java와 C++의 차이로 배우는 소멸자와 const 멤버 함수 (4) | 2024.10.31 |