반응형
기본 개념
복사 생성자( Copy Constructor )
-
객체가 자신과 동일한 타입으로 초기화 될때 사용
-
사용자가 생성하지 않으면 컴파일러가 자동 생성(Default Copy Constructor)
-
컴파일러가 제공하는 복사 생성자는 모든 멤버를 복사(Bitwise copy)
-
필요한 경우 사용자가 직접 정의 가능
복사 생성자의 형태
-
Point( const Point& p)
class Point
{
public:
int x;
int y;
public:
Point() : x(0), y(0) {} // 생성자 #1
Point(int a, int b) : x(a), y(b) {} // 생성자 #2
// 내부적으로 컴파일러에 의해 생성된 복사 생성자 형태
// Point(const Point& p): x(p.x), y(p.y) {}
};
int main()
{
Point p1; // 생성자 #1 호출
Point p2(1, 2); // 생성자 #2 호출
//Point p3(1); // 에러
Point p4(p2); // 컴파일러가 생성한 복사 생성자 호출
std::cout << p4.x << std::endl;
std::cout << p4.y << std::endl;
}
복사 생성자가 호출되는 경우
-
객체가 자신의 타입으로 초기화 될때
-
Point p2(p1);
-
Point p2{p1};
-
Point p2 = p1;
-
//객체가 자신의 타입으로 초기화 될때
class Point
{
int x, y;
public:
Point(int a = 0, int b = 0) : x(a), y(b)
{
std::cout << "Point()" << std::endl;
}
Point(const Point& p): x(p.x), y(p.y)
{
std::cout << "Point(Point&)" << std::endl;
}
};
int main()
{
Point p1(1, 2); // 생성자
Point p2(p1); // 복사 생성자
Point p3{ p1 }; // 복사 생성자
Point p4 = p1; // Point p4(p1) 복사 생성자
}
-
함수 인자로 객체를 값으로 받을 때( call by value )
-
const &를 사용하면 복사 생성자/소멸자의 호출을 막을 수 있음
-
void foo( const Point& p);
-
// 함수 인자로 객체를 값으로 받을 때( call by value )
void foo(Point pt) // 이 순간 복사 생성자 호출
{
}
int main()
{
Point p; // 생성자 호출
foo(p);
}
-
함수가 객체를 값으로 리턴할 때( return by value )
-
RVO / NRVO
-
g++ 사용시 -fno-elide-constructors 옵션 이용하면 확인 가능
-
// 함수가 객체를 값으로 리턴할 때( return by value )
Point foo()
{
Point pt(1, 2); // 생성자
return pt; // 리턴용 임시객체 생성
}
int main()
{
foo(); // 해당 라인동안만 임시객체는 유효
}
RVO(Return Value Optimization) / NRVO(Named RVO)
- 임시 객체(Temporary Object)
- "클래스이름()"으로 만드는 객체
- 사용자가 직접 만들거나 컴파일러에 의해 임시 객체를 만드는 코드가 생성되기도 함
- 단일 문장에서만 사용되며, 문장의 끝에서 파괴됨
- 함수가 참조로 리턴하면 리턴용 임시객체가 생성되지 않음
- 지역변수는 참조로 리턴할 수 없음
// RVO 적용 코드
Point foo()
{
return Point(1, 1); // 객체 생성자체를 임시 객체로 생성하여 복사 생성을 방지
}
int main()
{
foo();
}
// RVO 미적용 코드
// 내부 객체와 리턴용 임시 객체 총 2개가 생성된 코드
Point foo()
{
Point pt(1, 1); // pt 생성
return pt; // pt를 복사한 리턴용 임시 객체생성
}
int main()
{
foo();
}
Shallow Copy & Deep Copy
-
얕은 복사( Shallow Copy )
-
동적 할당된 메모리가 있을때 메모리 자체를 복사하지 않고 메모리 주소만 복사하는 현상
- 소멸자에서 메모리를 삭제 하는 경우 문제 발생
-
클래스 안에 포인터 멤버가 있고 동적 메모리 할당된 코드를 사용한다면 반드시 사용자가 복사 생성자를 만들어서 얕은 복사 문제를 해결 해야함
-
class Person
{
char* name;
int age;
public:
Person(const char* n, int a) : age(a)
{
name = new char[strlen(n) + 1];
strcpy_s(name, sizeof(name), n);
}
~Person() { delete[] name; }
};
int main()
{
Person p1("KIM", 2); // 단독 실행은 괜찮지만
Person p2 = p1; // 이렇게 복사할 경우 runtime error(name을 같은 주소값을 가르키고, 소멸자에서 이중 해지 발생)
}
-
깊은 복사( Deep Copy )
-
주소가 아닌 메모리 내용 자체를 복사하는 것
-
class Person
{
char* name;
int age;
public:
Person(const char* n, int a) : age(a)
{
name = new char[strlen(n) + 1];
strcpy_s(name, sizeof(name), n);
}
~Person() { delete[] name; }
// 복사 생성자 직접 구현(Deep Copy)
// 1. 포인터가 아닌 멤버는 그냥 복사(초기화 리스트)
Person(const Person& p): age(p.age)
{
// 2. 메모리 할당
name = new char[strlen(p.name) + 1];
// 3. 메모리 내용을 복사
strcpy_s(name, sizeof(name), p.name);
}
};
int main()
{
Person p1("KIM", 2);
Person p2 = p1; // runtime error
}
-
참조 계수(Reference Counting)
-
얕은 복사를 기본으로 참조 카운트를 공유하여 최종 소멸시에만 메모리는 해지 하는 기법
-
// 참조 계수(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_s(name, sizeof(name), n);
ref = new int;
*ref = 1;
}
// 소멸자에서 참조카운트 비교 후 최종 소멸시에만 동적 메모리 해지
~Person()
{
if (--(*ref) == 0)
{
delete[] name;
delete ref;
}
}
// 모든 멤버를 얕은 복사 후 참조 계수만 증가
Person(const Person& p): age(p.age), name(p.name), ref(p.ref)
{
++(*ref); // 참조 계수 증가
}
};
int main()
{
Person p1("KIM", 2);
Person p2 = p1; // 복사 생성자.
}
-
복사 금지(Delete Copy Constructor)
- 복사 생성자를 삭제(delete)
- 싱글턴 객체 생성시
- C++ 표준의 unique_ptr
- mutex 등의 동기화 객체
- 복사 생성자를 삭제(delete)
// 복사 금지
class Person
{
char* name;
int age;
int* ref; // 참조계수 메모리의 주소
public:
Person(const char* n, int a) : age(a)
{
name = new char[strlen(n) + 1];
strcpy_s(name, sizeof(name), n);
ref = new int;
*ref = 1;
}
// 소멸자 얕은 복사
~Person()
{
if (--(*ref) == 0)
{
delete[] name;
delete ref;
}
}
// 복사 생성자 삭제
Person(const Person& p) = delete;
};
int main()
{
Person p1("KIM", 2);
Person p2 = p1; // 복사 생성자.
}
- 소유권 이전(Move)
- 객체가 사용하던 자원을 전달 하는 개념
- 버퍼 복사 등의 일부 알고리즘에서 사용시 성능향상을 볼수 있음
- C++11의 move 생성자 개념
- 복사 후 원본 객체는 사용하지 못한다는 가정(swap에서 유용)
class Person
{
char* name;
int age;
public:
Person(const char* n, int a) : age(a)
{
name = new char[strlen(n) + 1];
strcpy_s(name, sizeof(name), n);
}
~Person() { }
// 소유권 이전의 복사 생성자
Person(Person& p): age(p.age), name(p.name)
{
// 원래 객체가 가진 name을 0으로
p.name = 0;
}
};
int main()
{
Person p1("KIM", 2);
Person p2 = p1; // 복사 생성자.
}
- C++ 표준라이브러리(STL) std::string을 활용
- 자체적으로 내부 버퍼관리가 되므로 얕은 복사시 문제 없음
class Person
{
std::string name;
int age;
public:
Person(std::string n, int a) : name(n), age(a)
{
}
};
int main()
{
Person p1("KIM", 2);
Person p2 = p1;
}
반응형
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ 연산자 재정의 - 기본 개념 (0) | 2019.05.08 |
---|---|
C++ static, const, this (0) | 2019.05.07 |
C++ 접근 지정자, 생성자, 소멸자 (0) | 2019.05.06 |
C++ OOP(Object Oriented Programming) (0) | 2019.05.06 |
C++ Explicit Casting (0) | 2019.05.06 |