new
new 의 동작 방식
- operator new() 함수를 사용해서 메모리 할당
- 메모리 할당이 성공하고 객체라면 생성자 호출
- 메모리 주소를 해당 타입으로 캐스팅 해서 리턴
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
형식 이름을 사용할 수 없습니다.
왜 생성자를 명시적으로 호출해야 하는가?
- 편하다 ```cpp #include
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.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]; } ```
댓글남기기