얕은 복사 문제(Shallow Copy)
클래스가 동적메모리 사용 후 복사시, 주소값만 복사가 되어서, 메모리가 삭제될때, 댕글링 포인터가 생기는 경우, 혹은 같은 동적 영역을 침범하는 경우.
template <typename T>
class smartptr {
private:
T* ptr;
public:
smartptr(T* p = 0) : ptr(p) {}
~smartptr() {
delete ptr;
}
T& operator*() { return *ptr;}
T& operator->() { return ptr;}
};
void func() {
smartptr<int> p1 = new int;
*p1 = 10;
cout << *p1 << endl;
smartptr<int> p2 = p1; // 복사!
cout << *p2 << endl;
}
int main()
{
cout << "F" << endl;
func();
cout << "Done" << endl;
}
저는 동적 메모리를 할당받아 만든 int* ptr 형을 가진 클래스 p1과, p1을 복사해서 만든 p2가 있습니다.
func() 함수는 스택에 올라가서, 함수가 끝나면 자연스레 클래스 소멸자가 실행되어,
p1의 소멸자 delete ptr이 실행이 되고, 이후 p2의 소멸자 delete ptr을 실행을 하려고 하는데?
ptr은 똑같은 곳을 가르키는 포인트이기 때문에, 그 공간을 두번 삭제하는 셈이 되어,
요렇게 런타임 오류가 뜹니다!
이런 얕은 복사 문제를 해결하는 4가지 방법이 있습니다.
깊은복사(Deep copy)
새로 메모리를 할당받고 내용까지 복사시키는 방법입니다.
template <typename T>
class smartptr {
private:
T* ptr;
public:
smartptr(T* p = 0) : ptr(p) {}
~smartptr() { delete ptr; }
// Deep copy
smartptr(const smartptr& p) {
ptr = new T;
memcpy(ptr, p.ptr, sizeof(T));
}
T& operator*() { return *ptr;}
T& operator->() { return ptr;}
};
요렇게 아예, 복사생성자 호출시에, memcpy를 하여서 내용을 복사시켜주었습니다.
참조계수(Reference counter)
포인터를 참조하는 카운터를 두고, 복사가 발생할때마다 1씩 증가시키는 방법입니다.
이는 몇개의 포인터가 참조되고 있는지 나타내서 사용할 수 있습니다.
template <typename T>
class smartptr {
private:
T* ptr;
int* ref;
public:
smartptr(T* p = 0) : ptr(p) { ref = new int(1); }
// Reference counter
smartptr(const smartptr& p): ptr(p.ptr), ref(p.ref) {
(*ref)++;
}
~smartptr() {
if (--(*ref) == 0) {
delete ptr;
delete ref;
}
}
T& operator*() { return *ptr;}
T& operator->() { return ptr;}
};
여러 인스턴스끼리 공간을 참조하도록 하고, 이렇게 ref 라는 레퍼런스를 두어서, 소멸자가 실행될때마다 -1 씩 줄어들게 만들고 결국 0이 될때, 이 포인터와 레퍼런스를 반환해줍니다.
shared_ptr<int> p1(new int); // 참조 계수복사
shared_ptr이라는 재사용하기 좋은 참조계수방법의 ptr도 있습니다.
소유권 이전(Transferring Ownership)
복사 발생시 이전 포인터의 연결을 아예 끊어버립니다.
template <typename T>
class smartptr {
private:
T* ptr;
public:
smartptr(T* p = 0) : ptr(p) {}
~smartptr() { delete ptr; }
// Transferring Ownership
smartptr(smartptr& p): ptr(p.ptr){
p.ptr = 0;
}
T& operator*() { return *ptr;}
T& operator->() { return ptr;}
};
아예 복사생성자를 할시에, 복사를 해준 p.ptr을 = 0 즉 NullPtr로 만들어서 이전 포인터는 쓰지 못하게 만듭니다.
unique_ptr<int> p1(new int); // 소유권 이전 복사
unique_ptr을 사용한다면, 유니크한 ptr을 사용할 수 있습니다.
복사금지(Preventing copy)
복사 생성자를 아예 private 영역에 선언하여, 복사를 못하게 만들어줍니다.
template <typename T>
class smartptr {
private:
T* ptr;
// Preventing copy
smartptr(const smartptr&);
void operator=(const smartptr&);
public:
smartptr(T* p = 0) : ptr(p) {}
~smartptr() { delete ptr; }
T& operator*() { return *ptr;}
T& operator->() { return ptr;}
};
이렇게 private 공간에 복사생성자를 넣어버린다던지,
혹은 c++11버전 이후로부터는
smartptr(const smartptr&) = delete;
복사생성자 = delete 를 써서 이런식으로 사용하기도 합니다.