8 분 소요

new 의 동작 방식

  1. operator new() 함수를 사용해서 메모리 할당
  2. 메모리 할당이 성공하고 객체라면 생성자 호출
  3. 메모리 주소를 해당 타입으로 캐스팅 해서 리턴

new 와 operator new() 는 다른 요소 이다.

#include <iostream>

class Point {
    int x, y;
public:
    Point() {
        std::cout << "생성자" << std::endl;
    }
    ~Point() {
        std::cout << "소멸자" << std::endl;
    }
};

int main() {
    Point* p1 = static_cast<Point*>(operator new(sizeof(Point)));
    operator delete(p1);

    Point* p2 = new Point;      // 생성자
    delete p2;                  // 소멸자
}

operator new()

#include <iostream>

// operator new() 는 재정의(overriding) 할 수 있다.
void* operator new(size_t size) {
    std::cout << "my new" << std::endl;
    return malloc(size);
}

// 오버로딩이 가능하다. 단 1번째 인자는 반드시 size_t 이어야 한다.
void* operator new(size_t size, const char* s, int n) {
    std::cout << "my new2 " << s << n << std::endl;
    return malloc(size);
}

// operator delete 도 재정의 가능 하다.
void operator delete(void* p) {
    std::cout << "my delete" << std::endl;
    free(p);
}

// 오버로딩도 가능하다. 1번째 인자는 반드시 void* 이어야 한다.
void operator delete(void* p, size_t size) {    // size 에 해제할 메모리 크기가 들어온다.
    std::cout << "my delete2 " << size << std::endl;
    free(p);
}

int main() {
    int* p1 = new("AAA ", 3) int;               // my new2 AAA 3
    delete p1;                                  // my delete2 4
}

#include <iostream>

class Point {
    int x, y;
public:
    Point() {
        std::cout << "생성자" << std::endl;
    }
    ~Point() {
        std::cout << "소멸자" << std::endl;
    }


};
// c++ 코드에 아래 함수가 이미 제공되고 있다.
// placement new 함수: 이미 존재하는 메모리에 생성자만 다시 호출하는 개념
//void* operator new(size_t size, void* p) {
//    std::cout << "생성자만 호출" << std::endl;
//    return p;
//}

int main() {
    Point p;            // 

    Point *p1 = new Point;  // 인자가 한개인 operator new() 호출
    new(&p) Point;          // 인자 2개인 operaotr new() 호출

    p.Point();              // error C7624: 형식 이름 'Point'은(는) 클래스 멤버 액세스 식의 오른쪽에 나타날 수 없습니다.
                            // 생성자의 명시적 호출? 
    p.~Point();             // 소멸자의 명시적 호출 ok.
}

error code

class Point { int x, y; public: Point(int a, int b) {} };

int main() { Point* p1 = new Point(1, 2); // 힙에 Point 한개 만들기. Point* p2 = new Point[10]; // error C2512: ‘Point’: 사용할 수 있는 적절한 기본 생성자가 없습니다. // 힙에 Point 10개 만들기: 기본 생성자가 있어야 한다.

// 해결책: 메모리 할당과 생성자 호출을 분리하면 편리해 진다.
Point* p3 = static_cast<Point*>(operator new(sizeof(Point) * 10));

// 할당된 메모리의 생성자를 호출 해서 객체를 초기화 한다.
for (int i = 0; i < 10; ++i) {
    new(&p3[i]) Point(0, 0);
}

// 이 경우, 소멸자 호출 및 메모리 해지도 아래처럼 해야 한다.
for (int i = 0; i < 10; ++i) {
    p3[i].~Point();             // 명시적 소멸자 호출
}
operator delete(p3);
delete[] p3;

Point p4[10];                   // error C2512: 'Point': 사용할 수 있는 적절한 기본 생성자가 없습니다.
                                // 스택에 생성시에도 기본 생성자가 없어 만들 수 없다.
char buf[sizeof(Point) * 10];
Point* p5 = reinterpret_cast<Point*>(&buf);
// 스택에 메모리를 잡은뒤 Point 로 캐스팅하여 사용할 수 있다.
// 이후 p5 에 대해 명시적 생성자를 호출 하고 사용후에 명시적 소멸자를 호출 해야 한다. } ``` [C2512 "Point" 클래스의 기본 생성자가 없습니다.]: https://docs.microsoft.com/ko-kr/cpp/error-messages/compiler-errors-2/compiler-error-c2512 > error code >- [C2512 "Point" 클래스의 기본 생성자가 없습니다.]() --- 생성자를 명시적으로 호출하여 사용하는 경우  - vector 의 메모리 관리 기술 ```cpp #include <iostream> #include <vector>

int main() { std::vector v(10, 3); // 10개를 3 으로 초기화

v.resize(7);

std::cout << v.size() << std::endl;     // 7
std::cout << v.capacity() << std::endl; // 10
                                        // 실제 메모리 크기
v.push_back(5);                         // 끝에 하나를 추가하고 5 로 초기화
std::cout << v.size() << std::endl;     // 8
std::cout << v.capacity() << std::endl; // 10

v.shrink_to_fit();                      // 사용하지 않는 공간 제거

std::cout << v.size() << std::endl;     // 8
std::cout << v.capacity() << std::endl; // 8

std::vector<int> v2(10, 0);
v2.push_back(1);                        // 커지는 경우
                                        // 이 경우, 기존 크기 * 1.5 를 주로 사용
                                        // 컴파일러 마다 다를 수 있다.
std::cout << v2.capacity() << std::endl;// 15

class DBConnect {};                     // vector 에 사용자 타입을 저장하는 경우
std::vector<DBConnect> v3(10);          // DBConnect 가 생성자에서 DB 에 접속한다고 가정.

v3.resize(7);                           // 줄어든 3개의 객체는 메모리에 분명 남아있다.
                                        // 하지만 소멸자를 호출 해서 DB 를 닫아야 한다.
                                        // 소멸자를 명시적 호출 해야 한다.
v3.resize(8);                           // 늘어난 한 개의 객체는 메모리에 이미 존재한다.
                                        // 하지만 생성자를 호출해서 DB 에 접속해야 한다.
                                        // 생성자를 명시적으로 호출 해야 한다. } ``` --- new 실패 ```cpp #include <iostream>

int main() { int *p1 = new(std::nothrow) int[100]; // 실패시 0 리턴.

int* p2 = 0;
try {
    p2 = new int[100];                  // 실패시 예외 던짐(throw)
    // ...
    delete[] p2;
} catch (std::bad_alloc& e) {
    std::cout << "메모리 부족" << std::endl;
} } ``` --- ```cpp #include <iostream>

void* operator new(size_t size) { void* p = malloc(size); if (p == 0) throw std::bad_alloc(); // 예외 전달. return p; }

// 사용되지 않고 함수 오버로딩을 위한 인자가 필요할 때에는 // 새로운 타입을 설계하는 것이 좋다: empty class

struct Nothrow {}; // empty class: 아무 멤버도 없다. Nothrow nothrow; // sizeof(nothrow): 1Byte

void* operator new(size_t size, Nothrow n) { void* p = malloc(size); return p; }

int main() { int* p = new(nothrow) int[100]; } ```

카테고리:

업데이트:

댓글남기기