✅코딩 규칙 : c++ 메모리 할당, placement new 등 new, delete 연산자.

보물창고·2022년 2월 27일
1

최종 problem

: Car 클래스 만들고, 생성자와 소멸자를 정의하라.
1) Car * p 를 operator new 연산자로 할당하고,
2) cout << "hello " << endl 출력하고
3) placement new 로 생성자 호출하고
4) 소멸자를 명시적으로 호출하고
5) 메모리를 명시적으로 해지하라 . : operator delete 연산자


아래 내용을 공부하면서 정리
할당 하는 방법 -> operator new (sizeof ( 모든 type ) )
해지 하는 방법 -> operator delete (sizeof ( 모든 type ) )
: 재정의함수를 만들 때는 호출하는 코드를 생각하면서 , 역으로 작성하면됨.

  • 보통 Point p = new Point(); 를 하는 거니까. 이 때 new 연산자는 Point 클래스를 반환함.
    : 그런데 이거는 c++ 에서 우리에게 편하게 사용하라고 하는 거고 결국에는
    malloc 의 반환처럼 void
    타입을 반환함.
    -> void * operator new(sizeof( Point))
    : sizeof를 size_t 로 변경

malloc

: 메모리만 할당함.

😀 c++에서의 new 의 동작방식

  1. 메모리를 먼저 할당함. : operator new() 연산자 함수를 사용함.
  2. 생성자를 호출함. : new (&p) Point(생성자 인자.)
  3. 메모리 주소를 해당 타입으로 반환함. : static_cast 사용

delete 의 동작 방식

  1. 소멸자를 먼저 호출함.
    : 객체의 멤버 데이터를 먼저 지워야 하잖아.
  2. 객체의 메모리를 해지함.
    : operator delete() 사용

동적할당 연산자 : void * operator new(size_t _Size)

특징.

  1. 동적할당할 때만 사용하자.
  2. 메모리만 할당하게 됨.
  3. 클래스를 대상으로 하더라도 반환값은 void * 형임.
    -> 반드시 static_cast를 사용해 형변환해야 함.

호출하는 방법.

operator new (sizeof(클래스명))

static_cast< 클래스명> ( operator new (sizeof(클래스명)) )

해지 연산자 : void operator delete(void * _pObj)

특징

  1. 메모리를 해제하는 부분임.
  2. 소멸자를 부르지는 않음.

호출하는 방법.

operator delete( 객체 명 );

problem

: operator new() 연산자 함수를 활용해 객체 Point p 를 동적할당하라.
-> 결과 : 생성자는 호출되지 않을 것임.

operator new동적할당과 일반 동적할당. 정적할당의 차이점.

  • operator new 동적할당
    : 멤버 내에서 초기화를 했지만, 초기화 이루어지지 않음.
    -> 생성자를 호출하지 않았고, 클래스 내로 진입하지도 않음.
  • 일반 new 동적할당.
    : 초기화 이루어짐.
    -> 컴파일러가 내부적으로 클래스를 진입했다는 것임.
  • 정적할당
    : 초기화 이루어짐.

결론
: 유저가 직접 new 연산자를 사용할 경우, 명시적으로 생성자를 호출해야 함.

  • 한개의 생성자만 호출하기.
    : 이거는 operator new 재정의를 공부한 다음에 하자...
  • operator new를 이용해 할당하는 방법은 유저 마음대로 조절이 가능함.

operator new를 재정의하기.

operator new를 어떻게 만들 것인가?

: 위에서 operator new(sizeof(Point))를 호출한 내용을 토대로
오버로딩을 만들면 됨

  • 추가적으로 반환값은 void* 라는 것을 인지하자!
  • malloc 한 결과를 그대로 반환

operator delete를 어떻게 만들 것인가?

: malloc 할 것을 해제하므로, free 사용.
:: noexcept를 사용해야 함.
-> 자원 해제시에 예외 처리 발생하면 , 메모리 누수 발생하기 때문임.

  • 예시 코드
  • 출력되는 것을 보면, 재정의한 new 함수가 먼저 호출됨을 확인할 수 있음.
    -> 기존 할당의 비밀 : new 연산자 뒤로 size_t 가 생략된 것임.
    : operator new(sizeof(Point));

operator new()는 여러 인자가 전달되도록 만들 수 있음.

  • new 연산자는 재정의할 수 없음. / operator new는 재정의가 가능함.

  • 인자의 갯수를 다르게 만들 수 있음.

  • 하지만 첫번째 인자는 반드시 size_t로 받아야 함.

-> 결과 : 위와 같이 호출하는 부분은 new 바로 다음에 괄호를 통해 호출할 수 있음...

도대체 왜 new 할당을 분류해서 사용하는 것일까...?
다음 시간에..

  • 위에서 메모리를 알아서 할당했지만, 생성자를 어떻게 부를 것인가?

생성자를 명시적으로 호출하기 위해서는

  • placement New를 사용하자!

Placement New 를 호출하는 방법.

new (객체의 주소) 클래스명

주의할점.

: 포인터로 만들었을때, 이렇게 하면 안됨..
-> 이렇게 하면, 포인터의 주소인데,,,

  • 포인터 자체가 주소이므로, 이렇게 하면 됨.

생성자와 소멸자를 명시적으로 호출하기

1.Placement New

: 생성자를 명시적으로 호출하는 방법.
:: 기존에 만들어진 객체를 이용해 생성자를 호출함.

  • 작성하는 방법.

    new( 객체의 주소 ) 클래스명

  • 예시
    : 밑의 그림을 보면, operator new 함수에서 메모리를 할당하지는 않았지만,
    기존에 만들어져 있는 p라는 객체를 이용해 생성자를 호출하고 있는 것을
    확인할 수 있음.

  • 결과

    • 생성자가 2번 호출되고, 소멸자가 2번 호출되고 있는 것을 확인할 수 있음.
      : 위 코드의 new 연산자 오버로딩의 경우는 이미 생성된 객체에 대해 생성자를
      호출해야 하므로, 메모리를 할당하고 있지 않음.
  • c++ 표준에 있음.

  • 결론
    : operator new 연산자로 동적할당 했을 때만 placement new 를 사용하자.

  • 아래 예시
    : 메모리를 새롭게 할당하라고 하는 것이 아닌, 생성자를 다시 한번
    호출하라고 하는 코드임.

2. 소멸자를 명시적으로 호출하기

사용하는 방법.

: 객체.~소멸자();

3. Problem

: Point 클래스를 만들고, 소멸자, 생성자 만들고, operator new 함수, operator delete 함수 만들자.
:: 그리고 호출하는 부분에서 동적으로 *p를 선언하고 operator new로 메모리를 할당하고, placement new로 생성자를 호출하고,
::: 소멸자를 직접 호출하고, 객체의 메모리를 해지하라.

  • 코드
    : 이때 new (&p)가 아니라 new (p)로 작성해야 함..

malloc vs new

  • malloc : 메모리만 할당.
  • new : 객체의 생성 (메모리 할당 + 생성자 호출)

메모리 할당 vs 생성자 호출

: 생성자 호출이 더 큰 역할을 함.

  • 네트워크에 접속

  • db 연결

  • 그림의 주석 내용을 보자!

    • 기존 메모리에 객체를 언제 생성할까???????
      : 다음 시간에..

placement new 필요한이유, 언제사용?

장점.

이점 : 메모리 관리측면이 아닌, 초기화 시 db연결 등 다른 객체와의 설정등을
하는 생성자 호출을 유저가 원하는 순간에 할 수 있음!

  • 도대체 왜 이러한 짓을 하는 거지???
    : 메모리와 생성자를 분리해서 따로 관리하기 위함.

  • 디폴트 생성자가 없는 클래스의 경우, 객체는 한개만 만들수 있음.

  • 디폴트 생성자가 없는 클래스에서 10개의 객체를 만들고 싶을 때
    불가능함! , 디폴트가 없어서 불가능한 상황임.

-> 이 때 메모리 생성과 생성자 호출을 분리하는 방법으로 해결이 가능함.

  • 아래의 그림이 분리하는 방법.
  • 벡터의 경우가 메모리 할당과 생성자 명시적 호출을
    분류해서 사용하고 있는 경우에 속함.

vector의 내부 동작
vectorv(10, Point(0,0)) ;
1) 10개의 메모리를 먼저 할당함.
2) Point(0,0) 클래스의 복사생성자를 받아서 생성자를 호출함.

벡터를 생각해보자.

  • 1) db에 10개 연결하는 코드가 있음.
    : 메모리를 10개 만들어 놓음.

  • 2) resize로 7개로 줄인다고 한다면? ,
    : 메모리는 줄어들지는 않지만, 3개의 연결된 상태는 끊어야 함!
    -> 3개의 소멸자를 불러서 연결을 끊자!
    --> resize하고 난후의 소멸자가 3개 호출되는 것을 확인할 수 있음.

    이때는 delete 를 호출하는 것이 아니라, 명시적으로 소멸자를 호출해야 함.
    ~p1.Point();

  • 3) size를 8로 변경한다면? 1개의 db 연결을 해야함.
    -> resize(8) 이후에 생성자가 1개 호출되는 것을 확인할 수 있음.

  • 결과 : 벡터의 resize의 경우, 작은 값으로 줄인다면, 메모리는 줄어들지 않지만, 소멸자가 호출되는 것을 확인할 수 있음.

  • 결론
    : 소멸자는 명시적으로 호출되어야 함.
    -> stl의 vector의 resize는 이를 이용하고 있음.

profile
🔥🔥🔥

0개의 댓글