일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- new&delete
- operator overloading
- delete function
- dynamic_cast
- virtual function
- this call
- virtual inheritance
- std::vector
- member function pointer
- std::ostream
- suffix return type
- virtual destructor
- c++ basic practice
- pointer to member data
- std::endl
- 더 지니어스 양면포커
- c++ multi chatting room
- C++
- std::cout
- discord bot
- virtual function table
- conversion constructor
- return by reference
- vector size
- increment operator
- placement new
- base from member
- vector capacity
- diamond inheritance
- constructor
- Today
- Total
I'm FanJae.
[C++ Intermediate] new/delete, placement new 본문
1. new와 delete
1-1. new와 delete의 원리
#include <iostream>
class Point
{
int x, y;
public:
Point(int a, int b) : x{a}, y{b} { std::cout << "Point(int, int)" << std::endl; }
~Point() { std::cout << "~Point()" << std::endl; }
};
int main()
{
Point* p1 = new Point(1,2);
delete p1;
}
Point* p1 = new Point(1,2);
- 위와 같이 쓰면 크게 2가지 작업을 진행한다. 즉, new를 실행하면 아래와 같은 2가지가 실행되는 것이다.
① 메모리할당
void *p = operator new(sizeof(Point))
② 생성자호출
Point *p1 = new(p) Point(1,2);
- 여기서 new(p) Point(1,2); 와 같은 표기법을 placement new라고 한다.
delete p1;
- 위와 같이 쓰면 크게 2가지 작업을 진행한다. 즉, delete를 실행하면 아래와 같은 2가지가 실행되는 것이다.
① 소멸자호출
p1->~Point()
② 메모리해지
operator delete(p1);
#include <iostream>
#include <new>
class Point
{
int x, y;
public:
Point(int a, int b) : x{a}, y{b} { std::cout << "Point(int, int)" << std::endl; }
~Point() { std::cout << "~Point()" << std::endl; }
};
int main()
{
void * p = operator new(sizeof(Point));
std::cout << p << std::endl;
operator delete(p);
}
- 따라서 이와 같은 행위도 가능하다는 의미다.
- C로 따지면 malloc과 free를 한 것과 유사하다.
- <new> 헤더를 포함하면 사용할 수 있다.
1-2. operator new() / operator delete()
#include <iostream>
#include <new>
#include <memory>
class Point
{
int x, y;
public:
Point(int a, int b) : x{a}, y{b} { std::cout << "Point(int, int)" << std::endl; }
~Point() { std::cout << "~Point()" << std::endl; }
};
int main()
{
void* p1 = operator new(sizeof(Point));
Point* p2 = new(p1) Point(1, 2); // 생성자 호출
operator delete(p1);
Point* p3 = static_cast<Point*>(operator new(sizeof(Point)));
std::construct_at(p3, 1, 2);
operator delete(p3);
}
- 메모리를 할당/해지하는 C++ 표준 함수이다.
- <new> 헤더에 있다.
- std namespace가 아닌 global namespace이다.
void* operator new(std::size_t);
void operator delete (void* ptr) noexcept;
- 함수 정의는 다음과 같다.
- 따라서 casting을 통해 casting된 값을 받는것도 가능하다.
① Placement new
- 메모리 할당없이 이미 할당된 메모리에 대해 생성자를 명시적으로 호출하기 위한 new
new Point(1,2); | 새로운 메모리를 할당하고 객체 생성 ( 생성자 호출 ) |
new(p) Point(1, 2); | 이미 할당된 메모리(p) 에 객체 생성 ( 생성자만 호출 ) placement new |
② 생성자를 명시적으로 호출하는 방법
new(p) Point(1,2); | |
std::construct_at(p, 1, 2); | C++20, <memory> |
③ 소멸자를 명시적으로 호출 하는 방법
p->~Point(); | |
std::destroy_at(p) | C++17, <memory> |
1-3. C++에서 객체를 생성/파괴하는 방법
① new/delete 사용
② 메모리 할당과 생성자 호출을 분리.
메모리 할당 | operator new(sizeof(Point)); |
생성자 호출 | new(p) Point(1, 2); placement new std::construct_at(p, 1, 2); C++ 20 |
소멸자 호출 | p->~point(); std::destory_at(p); // C++17 |
메모리 해지 | operator delete(p); |
1-4. Assembly Level
- 다음과 같이 placement new가 생성자 형태로 바뀐 것이다.
- delete의 경우도 소멸자를 부른 이후 ,operator delete(void *)를 진행한다.
- 이때 옵션에 -std=c++11로 해야만 이와 같이 나온다.
- 이는 다소 복잡한 얘기이므로, 뒤에서 다시 언급한다.
2. using placement new
2-1. 메모리 할당과 생성자 호출을 분리하는 이유
#include <iostream>
#include <new>
#include <memory>
class Point
{
int x, y;
public:
Point(int a, int b) : x{a}, y{b} { }
~Point() { }
};
int main()
{
// Point 객체 한개를 힙에 생성하고 싶다.
Point* p1 = new Point(0, 0);
// Point 객체 3개를 힙에
// 연속적으로 (배열 형태로) 생성하고 싶다.
Point* p2 = new Point[3];
}
- 왜 메모리 할당과 생성자 호출을 분리 하는가?
- 본 예제의 핵심은 Default 생성자가 없다는 것에 있다.
new Point[3];
- 이 경우 Point 타입에는 반드시 디폴트 생성자가 있어야 한다. 디폴트 생성자가 없으면 에러가 발생한다.
Point* p2 = new Point[3]={{0,0},{0,0},{0,0}}; // C++ 11
- 위와 같이 사용해도 문제는 없다. 문제는 C++ 11부터 사용 가능하고 3개가 아니라 개수가 커진다면 어떻게 해야할까?
- 이와 같은 상황일때 메모리 할당과 생성자 호출을 분리하면 훨씬 편하다.
① 해결법
#include <iostream>
#include <new>
#include <memory>
class Point
{
int x, y;
public:
Point(int a, int b) : x{a}, y{b} { }
~Point() { }
};
int main()
{
Point* p2 = static_cast<Point*> (operator new(sizeof(Point) * 3));
for (int i = 0; i < 3; i++)
{
// new(&p2[i]) Point(0, 0);
std::construct_at(&p2[i], 0, 0);
}
for (int i = 0; i < 3; i++)
{
p2[i].~Point();
std::destory_at(&p2[i]);
}
operator delete(p2);
}
- 즉, 메모리 할당과 생성자 호출을 분리하면 유연하게 객체 생성이 가능해진다.
2-2. 생성자와 소멸자만 호출하는 예시
#include <iostream>
#include <vector>
struct X
{
X() { std::cout << "X() get resource" << std::endl;
~X() { std::cout << "~X() release resource" << std::endl;
};
int main()
{
std::vector<int> v(10);
v.resize(7);
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
v.resize(8);
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
}
- 일반적으로, 크기가 줄어드는 경우, 실제 메모리를 줄이지 않고 size 변수 값만 변경한다.
- 보통 여기서 Capacity와 size 값의 차이가 드러난다.
#include <iostream>
#include <vector>
struct X
{
X() {
std::cout << "X() get resource" << std::endl;
}
~X() {
std::cout << "~X() release resource" << std::endl;
};
};
int main()
{
std::vector<X> v(10);
v.resize(7);
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
v.resize(8);
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
}
- int와 다르게 객체를 담는 경우라면, 메모리는 제거되지 않는다.
- 하지만 이와 별개로 줄어든 객체의 소멸자는 호출해야할 필요가 존재하지 않을까?
- 또, Resize()를 진행해서 크기가 늘어난다면 인위적으로 생성자만 호출해야 하지 않을까?
- 즉, 메모리의 적절한 관리에 따라서 필요하고, 개발자는 메모리 관리에 대한 부담 없이 벡터 사용이 가능하다.
#include <iostream>
#include <vector>
struct X
{
X() {
std::cout << "X() get resource" << std::endl;
}
~X() {
std::cout << "~X() release resource" << std::endl;
};
};
int main()
{
std::vector<X> v(10);
std::cout << "---------------" << std::endl;
v.resize(7); // 소멸자만 명시적으로 3번 호출되고 있다.
std::cout << "---------------" << std::endl;
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
std::cout << "---------------" << std::endl;
v.resize(8); // 생성자만 명시적으로 1번 호출된다.
std::cout << "---------------" << std::endl;
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
}
- vector는 사용자 정의 타입도 보관이 가능하다.
- 따라서 메모리의 할당 해지가 없음에도 생성자 / 소멸자만 부르는 행위를 진행하고 있다.
- 실행해보면 다음과 같음을 알 수 있다.
#include <iostream>
#include <vector>
struct X
{
X() {
std::cout << "X() get resource" << std::endl;
}
~X() {
std::cout << "~X() release resource" << std::endl;
};
};
int main()
{
std::vector<X> v(10);
std::cout << "---------------" << std::endl;
v.resize(7); // 소멸자만 명시적으로 3번 호출되고 있다.
std::cout << "---------------" << std::endl;
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
std::cout << "---------------" << std::endl;
v.resize(8); // 생성자만 명시적으로 1번 호출된다.
std::cout << "---------------" << std::endl;
std::cout << v.size() << std::endl; // 7
std::cout << v.capacity() << std::endl; // 10
}
- 이처럼, 메모리 관리 차원에서 사용될 수 있다.
'C++ > Intermediate' 카테고리의 다른 글
[C++ Intermediate] Member Function Pointer (2) | 2024.09.23 |
---|---|
[C++ Intermediate] this call (1) | 2024.09.22 |
[C++ Intermediate] Trivial (4) | 2024.09.19 |
[C++ Intermediate] 객체의 변환(Conversion) (0) | 2024.09.13 |
[C++ Intermediate] Constructor 생성 원리 (1) | 2024.09.10 |