티스토리 뷰
C++의 멤버함수 중 클래스 안에서 직접 선언하지 않으면 컴파일러가 자동으로 선언해주는 함수들
: 기본형 생성자 / 복사생성자 / 복사대입연산자 /소멸자
Class Empty{};
//위와 아래는 서로 같다
Class Empty
{
public:
Empty() {} // 생성자
Empty(const Empty& ) {} // 복사생성자
~Empty() {} // 소멸자
Empty& operator=(const Empty&) {} // 복사대입연산자
}
생성자
Empty() {}
멤버 변수의 초기화
메모리에 생성된 객체는 모든 멤버 변수를 초기화하기 전에는 사용할 수 없다. 특히 객체의 멤버 중 private 멤버에는 직접 접근할 수 없기 때문에 일반적인 초기화 방식으로 초기화가 불가능하다.
따라서 private 멤버에 접근할 수 있는 초기화만을 위한 public 함수가 필요하고, 이런 초기화 함수는 객체가 생성된 후부터 사용되기 전까지 반드시 멤버의 초기화를 위해 호출되어야 한다.
생성자(Constructor)
- C++에서 객체의 생성과 동시에 멤버 변수를 초기화해주는 멤버 함수. 객체 생성 시 딱 한 번 호출된다.
- 생성자를 별도로 구현하지 않으면 기본적으로 기본생성자를 자동으로 생성한다.
- 클래스 내부에 생성자를 직접 선언하면 컴파일러는 기본 생성자를 만들어주지 않는다.
- 매개변수 없이 인스턴스가 생성되는 것을 막을 때에는 기본생성자를 private로 선언하거나, public에서 선언 후 구현하지 않는다.
객체를 초기화하는 방법이 여러 개 존재할 경우에는 오버로딩 규칙에 따라 여러 개의 생성자를 가질 수 있다.
* 오버로딩: 중복정의(함수이름은 동일, 변수타입을 다르게) - 다양한 타입의 인수에 대해서 같은 기능을 하는 함수를 정의할 때 용이
* 오버라이딩: 재정의 - 자식이 부모를 무시할 때
멤버 이니셜라이저(member initializer)
객체 선언과 동시에 초기화가 이루어진다. 생성자 함수 뒤에 : 를 붙여서 사용한다.
class Zombie
{
private:
string type;
string name;
public:
Zombie(string type, string name);
};
// 생성자 함수 : 생성 후 초기화
Zombie::Zombie(string type, string name)
{
this->type = type;
this->name = name;
}
// 멤버 이니셜라이저 : 생성과 동시에 초기화
Zombie::Zombie(string type, string name):
type(type), name(name)
{}
멤버 이니셜라이저 사용 이유
1. 선언과 동시에 초기화가 이뤄지기 때문에 생성되는 바이너리 코드 양이 달라 성능에 이점이 있다.
2. const 멤버 변수 초기화 (* const: 변수를 상수로 선언해 값을 변경할 수 없게 해주는 키워드)
const 는 선언과 동시에 초기화가 이루어져야 하므로 멤버 이니셜라이저를 사용해야 한다.
class Test{
const int test;
Test(int x) : test(x){ }
};
3. 레퍼런스 멤버 변수 초기화
레퍼런스는 선언과 동시에 초기화가 이루어져야 한다. 레퍼런스 참고
class Test{
int &test;
Test(int x) : test(x) { }
};
4. 멤버 객체 초기화
5. 상속 멤버 변수 초기화 상속 참고
class Parent
{
public:
int x, y;
Parent(int i, int j)
{ x = i; y = j; }
};
class Test : public Parent
{
public:
int test;
Test(int i, int j, int k) : Parent(i, j)
{ test = k; }
};
얕은 복사 vs 깊은 복사
얕은 복사
- 단순 변수를 복사하는 경우에는 문제가 없다.
- 멤버 변수가 힙의 메모리 공간을 참조하는 경우에 문제가 된다. (할당, 포인터 등) 참고
- (컴파일러가 자동적으로 생성해주는) 기본 복사생성자, 기본 복사대입연산자 사용 시 얕은 복사가 일어난다.
깊은 복사
- 기본복사생성자, 기본대입연산자를 오버로딩해서 깊은 복사를 구현한다.
복사생성자
Empty(const Empty& ) {}
복사생성자가 변수를 참조로 받는 이유: 복사 무한루프에 빠지지 않게 하기 위해서
복사대입연산자
Empty& operator=(const Empty&) {}
연산자 오버로딩
(리턴 타입) operator(연산자) (연산자가 받는 인자)
대입연산자가 참조로 리턴하는 이유: 대입 연산이 사슬처럼 엮이는 상황에서의 관례
자기대입
자기 자신에 대해 대입연산자를 사용하는 경우를 방지해야 한다.
- 원본 객체와 복사대상 객체의 주소 비교
- 문장의 순서 조절
- 복사 후 맞바꾸기
대입연산자 vs 복사생성자
- If a new object has to be created before the copying can occur, the copy constructor is used (note: this includes passing or returning objects by value).
- If a new object does not have to be created before the copying can occur, the assignment operator is used.
복사를 하면서 새 객체의 생성이 필요하면 복사생성자를 사용하고, 그렇지 않으면 대입연산자를 사용한다.
int main()
{
// 복사생성자
Dot dot1(1, 2);
Dot dot2 = dot1;
// 대입연산자
Dot dot1(1, 2);
Dot dot2(3, 4);
dot2 = dot1;
}
복사 생성자는 객체가 새로 생성되는 시점에서 대입을 할 때 호출이 되고 대입 연산자는 객체 두 개가 이미 생성 및 초기화가 진행된 상태에서 대입을 할 때 호출이 된다.
'42 > cpp' 카테고리의 다른 글
[C++] NULL 포인터로 객체의 함수가 호출되는 이유 (0) | 2021.07.21 |
---|---|
참조자 (0) | 2021.05.26 |
메모리구조, 정적할당과 동적할당 (0) | 2021.05.25 |
객체지향, 접근지정자 (0) | 2021.05.25 |
네임스페이스, 표준입출력 (0) | 2021.05.18 |