전방선언 : 식별자를 정의하기 전에 식별자의 존재를 컴파일러에게 알리는 것

헤더 포함 의존성을 최소화하기 위해 사용한다.

 

class Game
{
public:
	Game() {}
	~Game() {}

private:
	Player* _player; // Player 헤더가 없으면 컴파일이 불가능
};

 

class Player; // 나중에 정의하겠다고 전방선언

class Game
{
public:
	Game() {}
	~Game() {}

private:
	Player* _player;
    // 최상단 줄을 지우고 class Player* _player 와 같은 형태도 가능하다.
};

 

물론 헤더파일을 포함시키면 해결 되지만, include의 의존성을 줄임으로써 빌드 시간을 줄일 수 있다.

멤버 변수로 선언시에는 포인터인 경우만 가능하고 리턴 타입이나 매개 변수의 경우 객체 타입으로 사용할 수 있다.

 

단, 동적 할당이나 호출하는 경우는 데이터 구조를 모르기때문에 오류가 발생하게 된다.

 

"해당 클래스는 몇 바이트인가?" 라는 질문을 던져보면 알 수 있다.

'C++ > Rookiss C++' 카테고리의 다른 글

[콜백 함수] 함수 포인터  (0) 2022.08.30
[디버깅]  (0) 2022.08.30
[동적 할당] 캐스팅 4총사  (0) 2022.08.29
[동적 할당] 얕은 복사 vs 깊은 복사  (0) 2022.08.29
[동적 할당] 타입 변환  (0) 2022.08.29

면접에서 매번 나오는 단골 질문이다.

 

static_cast

타입 원칙에 비춰볼 때 상식적인 캐스팅만 허용해준다. C스타일의 타입 변환을 대체한다고 보면 된다.

ex) int <-> float, 다운 캐스팅

 

dynamic_cast

상속 관계에서의 안전한 형 변환을 지원한다.

RTTI(Runtime Type Information). 다형성을 활용한다. 즉, 가상 함수가 존재하는 클래스에만 사용할 수 있다.

vftable을 이용한다.

만약 잘못된 타입으로 캐스팅 시, nullptr을 반환해준다.

 

const_cast

const를 붙이거나 뗄 때 사용한다.

사용할 일은 거의 없다고 보면 된다.

 

reinterpret_cast

가장 위험하고 강력한 형태의 캐스팅.

ex) 포인터랑 전혀 관계없는 다른 타입 변환 등

'C++ > Rookiss C++' 카테고리의 다른 글

[디버깅]  (0) 2022.08.30
전방선언  (0) 2022.08.29
[동적 할당] 얕은 복사 vs 깊은 복사  (0) 2022.08.29
[동적 할당] 타입 변환  (0) 2022.08.29
[동적 할당] 동적 할당  (0) 2022.08.28

복사 생성자와 복사 대입 연산자는 정의하지 않아도 컴파일러가 암시적으로 만들어준다.

이 경우 얕은 복사가 일어난다.

 

얕은 복사 (Shallow Copy)

멤버 데이터를 비트열 단위로 똑같이 복사한다. (메모리 영역 값을 그대로 복사)

기본 복사 생성자나 기본 복사 대입 연산자는 값을 그대로 복사해준다.

 

깊은 복사 (Deep copy)

멤버 테이터가 참조(주소) 값이라면 데이터를 새로 만들어준다. (원본 객체가 참조하는 대상까지 새로 만들어서 복사)

 

복사 생성자를 암시적으로 사용하는 경우에는 부모 클래스와 멤버 클래스의 복사 생성자가 호출되지만, 명시적으로 사용하는 경우 부모 클래스와 멤버 클래스의 기본 생성자가 호출된다.

명시적으로 사용 시 부모와 멤버 클래스의 복사 생성자를 호출하기 원한다면 초기화 리스트에서 지정해 주어야 한다.

 

암시적 복사 생성자와 복사 대입 연산자

  1. 부모 클래스의 복사 생성자 호출
  2. 멤버 클래스의 복사 생성자 호출
  3. 멤버가 기본 타입일 경우 메모리 복사 (얕은 복사)

 

명시적 복사 생성자

  1. 부모 클래스의 기본 생성자 호출
  2. 멤버 클래스의 기본 생성자 호출
// 복사 생성자를 명시적으로 선언한 경우에는,
// 부모 클래스나 멤버 클래스의 복사 생성자를 초기화 리스트로 호출해주어야 한다.
Knight(const Knight& knight) : Player(knight), _pet(knight._pet)
{
    cout << "Knight(const Knight&)" << endl;
    _hp = knight._hp;
}

 

명시적 복사 대입 연산자 : 아무것도 해주지 않는다.

// 복사 대입 연산자를 명시적으로 선언한 경우에는,
// 부모 클래스와 멤버 클래스의 복사 대입 연산자를 호출해주어야 한다.
Knight& operator=(const Knight& knight)
{
    cout << "opeator=(const Knight&)" << endl;
    Player::operator=(knight);
    _pet = knight._pet;
    _hp = knight._hp;
    return *this;
}

 

깊은 복사를 선택하게 되는 경우 필연적으로 명시적인 복사 생성자 또는 복사 대입 연산자가 필요하게 되는데, 그 순간 모든 책임은 프로그래머에게 위임된다.

'C++ > Rookiss C++' 카테고리의 다른 글

전방선언  (0) 2022.08.29
[동적 할당] 캐스팅 4총사  (0) 2022.08.29
[동적 할당] 타입 변환  (0) 2022.08.29
[동적 할당] 동적 할당  (0) 2022.08.28
[객체지향 여행]  (0) 2022.08.28

값 타입 변환

의미를 유지하기 위해서, 원본 객체와 다른 비트열을 재구성

ex) int를 float형으로 변환시 비트의 구성이 다르기때문에 비슷한 값으로 비트열이 재구성 됨

 

참조 타입 변환

비트열을 재구성하지 않고, 관점만 바꾸는 것

ex) int를 float형의 레퍼런스로 변환시 비트는 그대로지만 비트의 구성이 다르기때문에 출력 값이 다르다.

실제로 사용하는 일은 거의 없을 것이다.

 

안전도 분류

 

안전한 변환: 의미가 항상 100% 일치하는 경우

ex) int를 long long으로 변환

 

불안전한 변환: 의미가 항상 100% 일치한다고 보장하지 못하는 경우

타입이 다르거나 더 작은 크기로 변환할 때

ex) int를 float로 변환

 

프로그래머의 의도에 따른 분류

 

암시적(implicit) 변환: 컴파일러가 자동으로 타입 변환

 

명시적(explicit) 변환: 사용자가 명시적으로 타입 지정

 

아무런 연관 관계가 없는 클래스 사이의 변환

일반적으로는 안되지만 타입 변환 생성자/연산자로 가능하게 만들어 줄 수 있다.

참고) 타입 변환 연산자는 반환값이 없다.

operator Knight()
{
	return (Knight)(*this);
}

 

연관 없는 클래스 사이의 참조 타입 변환

명시적으로 변환하는것은 가능하다. (=문법상으로는 문제 없다.)

 

상속 관계에 있는 클래스 사이의 변환

1) 상속 관계 클래스의 값 타입 변환 : 자식->부모(O) 부모->자식(X)

2) 상속 관계 클래스의 참조 타입 변환: 자식->부모(O) 부모->자식(암시적X, 명시적O)

 

 

크기가 큰 객체를 함수의 인자로 넘겨줄 때 값에 의한 복사로 넘겨주면 오버헤드가 심하게 발생하기 때문에 포인터로 넘겨주어야 한다.

 

명시적으로 타입을 변환할 때는 항상 조심해야만 한다.

객체를 동적 할당할 때 업캐스팅, 사용할 때 다운캐스팅을 하는 경우가 많기 때문에 타입 변환에 의한 실수가 일어날 가능성이 높다.

 

업캐스팅으로 동적 할당한 객체는 별다른 처리 없이 메모리 해제시에는 업캐스팅 대상 클래스의 소멸자까지만 호출이 된다.

따라서 상속 관계가 있는 클래스의 경우 부모 클래스(또는 최상위 클래스)의 소멸자를 가상 소멸자로 지정해주면 소멸자가 의도한 대로 잘 작동하게 된다.

 

가상 소멸자에 대한 내용은 면접에 거의 항상 나오는 단골 질문이다.

'C++ > Rookiss C++' 카테고리의 다른 글

[동적 할당] 캐스팅 4총사  (0) 2022.08.29
[동적 할당] 얕은 복사 vs 깊은 복사  (0) 2022.08.29
[동적 할당] 동적 할당  (0) 2022.08.28
[객체지향 여행]  (0) 2022.08.28
[포인터] #2/2  (0) 2022.08.27

[동적 할당]

 

스택 영역은 다량의 메모리를 사용하기에 적합하지 않고, 데이터 영역은 사용자의 이용 여부와 관계없이 무조건 할당되어 사용되기 때문에 비효율적이다.

 

사용자가 원할 때 할당 및 해제해서 사용할 수 있는 영역을 힙 영역이라고 한다.

[ 코드영역  ] [     데이터 영역     ] [ 힙 영역  ] [ 스택 영역 ]

[   code   ] [.data][.bss][.rodata] [  heap   ] [   stack   ]

 

C++에서는 기본적으로 CRT(C Runtime Library)의 힙 관리자를 통해 힙 영역을 사용한다.

정말 원한다면 직접 API를 통해 힙을 생성하고 관리할 수도 있다. (ex: MMORPG 서버 풀링)

 

malloc - free

 

malloc

메모리 할당 후 시작 주소를 가리키는 void형 포인터를 반환해준다.

메모리가 부족하면 null을 반환한다.

 

free

메모리 해제시 크기를 지정하지 않아도 알아서 해제되는 이유는 할당할 때 헤더 영역을 추가로 만들어서 정보를 보관해두기 때문이다.

같은 메모리를 두 번 해제하는 것을 double free라고 하는데 보통 크래시만 발생한다. 첫 번째 free시 헤더 영역의 정보까지 모두 날아가므로 두 번째 free는 정보가 없어서 해제를 할 수가 없다.

 

use after free가 발생하는 것을 유의해야 한다.

 

new - delete

 

malloc-free는 함수이고 new-delete는 연산자이다.

사용 편의성은 new-delete가 좋지만 타입에 상관없이 특정한 크기의 메모리 영역을 할당 받으려면 malloc을 이용해야 한다.

 

malloc-free와 new-delete의 결정적인 차이

"new-delete는 생성 타입이 클래스인 경우 생성자와 소멸자를 호출해준다."

'C++ > Rookiss C++' 카테고리의 다른 글

[동적 할당] 얕은 복사 vs 깊은 복사  (0) 2022.08.29
[동적 할당] 타입 변환  (0) 2022.08.29
[객체지향 여행]  (0) 2022.08.28
[포인터] #2/2  (0) 2022.08.27
[포인터] #1/2  (0) 2022.08.26

+ Recent posts