일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 더 지니어스 양면포커
- delete function
- c++ basic practice
- std::vector
- virtual function table
- vector capacity
- new&delete
- suffix return type
- virtual destructor
- member function pointer
- virtual function
- base from member
- std::ostream
- std::cout
- c++ multi chatting room
- this call
- conversion constructor
- placement new
- pointer to member data
- diamond inheritance
- return by reference
- dynamic_cast
- vector size
- increment operator
- C++
- discord bot
- virtual inheritance
- operator overloading
- std::endl
- constructor
- Today
- Total
I'm FanJae.
[C++] Copy Constructor 본문
※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.
1. Copy Constructor
- 자신과 동일한 타입 한개를 인자로 가지는 생성자
class Point
{
int x;
int y;
public:
Point() : x{0}, y{0} {} // 1
Point(int a, int b) : x{a}, y{b} {} // 2
};
int main()
{
Point p1; // ok.
Point p2(1,2); // ok
// Point p3(1); // error. Point(int) 필요
Point p4(p2); // ok. Point(Point)
}
- Point p4(p2);와 같은 생성자는 만든적이 없음에도 이상없이 잘 작동한다.
※ 복사 생성자를 만들어주지 않으면, 어떻게 생성해주는가?
- 컴파일러가 제공한다.
- 디폴트 복사 생성자 ( default copy constructor )
- 모든 멤버를 복사(bitwise copy)를 진행한다.
1-1. 복사 생성자가 호출되는 3가지 경우
① 자신과 동일한 타입의 객체로 초기화 될 때
→ Point p2(p1);
→ Point p2{p1};
→ Point p2 = p1;
class Point
{
public:
int x;
int y;
Point(int a, int b) : x(a), y(b)
{
std::cout << "ctor" << std::endl;
}
Point(const Point& p) : x(p.x), y(p.y)
{
std::cout << "copy ctor" << std::endl;
}
};
int main()
{
Point p1(1,2); // 생성자
Point p2(p1); // 복사 생성자
Point p3{p1}; // 직접 초기화
Point p4 = {p1}; // 복사 초기화
Point p5 = p1; // 복사 초기화
}
② 함수 인자를 call by value로 받을 경우
class Point
{
public:
int x;
int y;
Point(int a, int b) : x(a), y(b)
{
std::cout << "ctor" << std::endl;
}
Point(const Point& p) : x(p.x), y(p.y)
{
std::cout << "copy ctor" << std::endl;
}
};
// void foo ( Point pt) // Point pt = p1
void foo( const Point& pt) // const Point& pt = p1
{
}
int main()
{
Point p1(1,2);
foo(p1);
}
- 함수 인자를 const reference로 사용하면 복사본을 만들지 않는다. 따라서 복사 생성자가 호출되지 않는다.
③ 함수 인자를 call by value로 받을 경우
Point p; // 생성자
Point foo() // 값 타입 반환
{
return p;
}
int main()
{
foo(p1);
}
- 이와 같이 리턴용 임시객체가 생성될때 복사 생성자가 호출된다.
- 한편 참조로 반환하면 리턴용 임시객체가 생성되지 않는다.
- 단, 지역변수는 절대 참조로 반환하면 안된다.
2. Default Copy Constructor의 문제점 (Shallow Copy)
#include <iostream>
#include <cstring
class Person
{
char* name;
int age;
public:
Person(const char* n, int a) : age(a)
{
name = char[strlen(n) + 1];
strcpy(name, n);
}
~Person() { delete[] name; }
}
int main()
{
Person p1("kim",20);
Person p2 = p1; // 실행시 오류 발생
}
- 일반적으로 객체가 자신의 동일한 타입의 객체로 초기화 될 때 이와 같은 작업이 이루어진다.
- 복사 생성자가 사용되고, 사용자가 만들지 않은 경우 디폴트 복사 생성자가 사용된다.
- 디폴트 복사 생성자는 모든 멤버를 복사한다.
문제는 디폴트 복사 생성자가 모든 멤버를 복사해주기 때문에 발생하는 문제가 있다.
Person(const char* n, int a) : age(a)
{
name = char[strlen(n) + 1];
strcpy(name, n);
}
- 이와 같은 상황일때, p2 = p1; 과 같은 일이 일어났다면, 아래와 같이 복사된다.
- p1에 있는 "kim"이라는 문자에 대해서 새롭게 동적 할당 해주는 것이 아닌 기존 p1이 바라보던, kim을 그대로 복사한다.
- 이와 같이, 클래스 안에 포인터 멤버가 있을때 디폴트 복사 생성자가 메모리 자체를 복사하지 않고, 주소만 복사하는 현상을 얕은 복사라고 한다.
- 이 경우, 개발자가 직접 복사 생성자를 만들어야 한다.
3. Default Copy Constructor를 해결할 방법
3-1. 깊은 복사( Deep Copy)
- 기존 Default Copy Constructor를 해결 할 방법으로, 클래스 안에 포인트 멤버가 있을때, 메모리 주소를 복사 하지 말고 메모리 자체의 복사본을 만드는 기술을 이용해야한다.이를 깊은 복사(Deep Copy)라고 한다.
- 즉, 아래 그림과 같이 p1,p2가 별도의 포인터 멤버에 대해서 별도의 공간을 할당 하도록 만들어야 한다.
Person(Const Point& p)
{
age = p.age;
// name = p.name; // 이렇게 하지말고.
// 포인터는 복사 하지말고 새롭게 메모리를 할당
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
}
3-1-1.깊은 복사 ( Deep Copy ) 의 단점
- 객체를 여러 번 복사하면 동일한 자원(이름)이 메모리에 여러번 놓이게 된다.
- 자원이 작으면 상관이 없지만, 자원이 크면 상당히 낭비된다.
- 이러한 단점을 해결하는 것이 바로, 참조 계수(Reference Counting)이다.
3-2. 참조 계수 (Reference Counting)
- 여러 객체가 하나의 자원을 공유하게 된다.
- 몇 명의 객체가 자원을 사용하는지 개수를 관리한다.
class Person
{
char* name;
int age;
int* ref;
public:
Person(const char* n, int a) : age(a)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
ref = new int(1);
//*ref = 1;
}
~Person()
{
// ref의 참조계수가 0이면 더이상 생성된 객체가 없음을 의미
if(--(*ref) == 0)
{
delete[] name;
delete ref;
}
Person(const Person &p) : name(p.name), age(p.age), ref(p.ref)
{
++(*ref);
}
}
- 이와 같이 작성을 해주더라도 문제가 존재한다.
- p1 객체가 만약 '자신의 이름을 변경'한 상황이라고 생각해보자.
- p2의 이름은 변경되면 안되므로 공유했던 자원은 분리된다.
- 이 문제의 경우, 멀티 스레드 환경에서는 동기화의 오버헤드가 추가된다.
3-3. 복사 금지
Person(const Person&) = delete;
- 객체를 복사하지 못하게 하자는 의도이다. 복사 생성자를 delete 한다.
- 이 경우, 컴파일러가 복사 생성자를 만들지 않는다
3-4. STL의 string 클래스 사용
class Person
{
std::string name;
int age;
public:
Person(std::string n, int a) : name(n), age(a)
{
}
}
- 이 경우, 동적 메모리 할당을 할 필요가 없다.
- string이 내부적으로 자원을 알아서 관리해주고, int 변수 처럼 사용이 가능하다.
※ move라는 기능이 있기는 하나, 중급 단계 정도에서 별도 포스트로 다뤄보고자 한다.
'C++ > Basic' 카테고리의 다른 글
[C++] Upcasting, Virtual Function (0) | 2024.08.21 |
---|---|
[C++] Inheritance(상속) (0) | 2024.08.20 |
[C++] this, Reference return (1) | 2024.08.18 |
[c++] const member function, mutable (0) | 2024.08.17 |
[C++] Static Member (0) | 2024.08.16 |