温故而知新
一,引子
C++ 里调用C的函数,这个C函数返回的malloc内存。如何避免内存泄漏? 仅靠手搓free,肯定不靠谱。万一在free前return那会造成泄漏...
如何管理malloc的内存,防止内存泄漏,是个值得思考的问题。
方法应该有几种,比如function object,智能指针... 选择了std::unique_ptr 来管理malloc的内存对象。
template<class T, class Deleter = [std::default_delete]<T>>
class unique_ptr;
template <class T, class Deleter>
class unique_ptr<T[], Deleter>;
//通常情况可以创建如下unique_ptr对象:
std::unique_ptr<int[]> uq(new int[8]);
问题先留一会,结尾再回答这个问题。这里就引出了new 关键词。
二,new 表达式
2.1 创建对象
new 是用来创建并初始化动态时长的对象,生命周期不跟随所在函数的生命周期。new 是基于堆上创建,堆上对象并不跟随函数本身结束而释放,所以要注意销毁,以防内存泄露.
new 创建对象的方法:
new (type) new-initializer(optional)
new type new-initializer(optional)
new (placement-args)(type)new-initializer(optional)
new (placement-args) type new-initializer(optional)
Type :类型除了常规的基本数据类型,还可以是数组,可以是类型推导占位符(auto decltype),可以是模板名。 比如模板类型:
template <class T>
class MyClass {T m;public: MyClass(T p):m(p){};};
MyClass<int>* ptr = new MyClass(10);
后面两种是比较少见的placement new。第三节再举例。
initializer :new 初始化,对象的初始化可以用小括号()或{}大括号。 小括号初始化的劣势,对象的初始化和函数申明容易混乱。比如:
TestClass obj();
可能被解析成函数申明。 如果类存在std::initializer_list
类型的构造函数。大括号{}形式的构造对象,将优先使用初始化列表的构造函数。
class TestClass {
public:
TestClass(int a, int b) {
std::cout << "Regular constructor called with a = " << a << ", b = " << b << std::endl;
}
TestClass(std::initializer_list<int> initList) {
std::cout << "Initializer list constructor called with elements: ";
for (auto elem : initList) { std::cout << elem << " "; }
std::cout << std::endl;
}
};
TestClass obj{10, 20}; // 调用初始化列表构造函数,因为{10, 20}是一个初始化列表
2.2 分配内存
new 表达式会使用适当的内存分配函数来创建内存, 这种函数学名叫operator new,如果创建的是数组则称之为:operator new[]. new 表达式会把对应size传递给operator new,一般是sizeof(T). 对于这节来说,需要注意的只有一点:局部和c++全局的operator new. 两者在使用方式上注意要点如下:
1, 操作符"::"
如果在创建对象的时候,new前加"::"修饰则表示:你指定的是全局的那个operator new,换言之,你想使用的是c++库提供的new。编译的时候也会忽略局部或者类里的operator new。
2, 没有操作符"::"
如果没有局部的operator new,默认会使用全局,当存在局部的,则会优先使用局部的operator new.
#include <iostream>
#include <memory>
class TestClass {
public:
TestClass(int a=0, int b=0) {
std::cout << "Regular constructor called: "<<this<<", a="<< a << ", b = " << b << std::endl;
}
~TestClass() {
std::cout << "~ Destructor called: "<<this << std::endl;
}
TestClass(std::initializer_list<int> initList) {
std::cout << "Initializer list constructor called with elements: "<<this<<", ";
for (auto elem : initList) { std::cout << elem << " "; }
std::cout << std::endl;
}
void* operator new(size_t count) {
std::cout<<"private new:"<<count<<std::endl;
return ::operator new(count);
}
void operator delete(void *p) {
std::cout<<"private delete:"<<p<<std::endl;
::operator delete(p);
}
int m;
};
int main()
{
TestClass* ptr1 = new TestClass;
TestClass* ptr2 = ::new TestClass{2,2};
delete ptr2;
::delete ptr1;
return 0;
}
private new:4
Regular constructor called: 0x57fb0b7356c0, a=0, b = 0 //局部operator new ,ptr1
Initializer list constructor called with elements: 0x57fb0b7356e0, 2 2 //全局operator new,ptr2
~ Destructor called: 0x57fb0b7356e0
private delete:0x57fb0b7356e0//局部 delete ,ptr2
~ Destructor called: 0x57fb0b7356c0 //全局delete ,ptr1
可以得到以下几点:
1, 先分配内存,后调用构造函数
2, 先析构,后调用operator delete
3, operator new 和 operator delete 不必一一对应.甚至,可以不定义局部delete 也是可以的。
三, placement new
new 表达式的第4种:
::(optional) new (placement-args) type new-initializer (optional)
相对常用的创建方式多了一个参数placement-args placement new 是一种特殊的operator new,如何理解operator new和placement new的关系呢? 就好比,正方形是特殊的矩形。
placement new 方式的特殊地方在于: 在已分配的内存地址上(placement-args),存放新的数据。
有了这层理解就很简单了。
class TestClass {
public:
TestClass(int a=0, int b=0) {
std::cout << "Regular constructor called: "<<this<<", a="<< a << ", b = " << b << std::endl;
}
~TestClass() {
std::cout << "~ Destructor called: "<<this << std::endl;
}
TestClass(std::initializer_list<int> initList) {
std::cout << "Initializer list constructor called with elements: "<<this<<", ";
for (auto elem : initList) { std::cout << elem << " "; }
std::cout << std::endl;
}
void* operator new(size_t count) {
std::cout<<"private new:"<<count<<std::endl;
return ::operator new(count);
}
void* operator new(size_t count,void *p) {
std::cout<<"Local-Placement new:"<<count<<std::endl;
return p;
}
void operator delete(void *p) {
std::cout<<"private delete:"<<p<<std::endl;
::operator delete(p);
}
int m;
};
int main()
{
TestClass* ptr1 = new TestClass;
TestClass* ptr2 = ::new TestClass{2,2};
std::cout<<">>>>>>test placement case:\n";
TestClass* ptr3 = new (ptr1) TestClass {30,30};
TestClass* ptr4 = ::new (ptr2) TestClass {40,40}; //to call global
std::cout<<"p3: "<<ptr3<<" ,p4: "<<ptr4<<std::endl;
std::cout<<">>>Destructor<<<\n";
delete ptr2;
::delete ptr1;
return 0;
}
ptr3用的是局部的placement new, ptr4使用的是全局的::placement new
placement-args 参数分别是ptr1, ptr2。
如结果: ptr3==ptr1 , ptr4 == ptr2
private new:4
Regular constructor called: 0x5e60cb37e6c0, a=0, b = 0
Initializer list constructor called with elements: 0x5e60cb37e6e0, 2 2
>>>>>>test placement case:
Local-Placement new:4
Initializer list constructor called with elements: 0x5e60cb37e6c0, 30 30
Initializer list constructor called with elements: 0x5e60cb37e6e0, 40 40
p3: 0x5e60cb37e6c0 ,p4: 0x5e60cb37e6e0
>>>Destructor<<<
~ Destructor called: 0x5e60cb37e6e0
private delete:0x5e60cb37e6e0
~ Destructor called: 0x5e60cb37e6c0
placement new 唯一要注意的点:placement-args必须是有效的堆上地址,否则都是未定义行为。注意调用delete,析构也会调用。
placement new,不好用,用的不好, 潜在风险点是什么?欢迎留言讨论...
四, 如何使用unique_ptr 管理malloc 堆上的内存
new 匹配 delete, malloc 匹配 free. 所以原则上malloc的内存需要用free来释放。
根据unique_ptr的定义,可以自定义deleter,即:
std::unique_ptr<int[],void(*)(void*)> uq(ptr,free);
#include <iostream>
#include <memory>
#include <cstring>
class HeapBuffer {
int *mptr;
public:
HeapBuffer(int*p=nullptr):mptr(p){};
~HeapBuffer(){
std::cout<<"destructor,mptr:"<<mptr<<std::endl;
if (mptr!=nullptr) {
std::cout<<"to free mptr:"<<mptr<<std::endl;
free(mptr);
mptr=nullptr;
}
}
};
void fun(int* ptr){
//std::unique_ptr<int[],void(*)(void*)> up(ptr,free);//方法1
HeapBuffer buff(ptr); //方法2
}
int main() {
// Write C++ code here
int *ptr = (int*)malloc(sizeof(int)*10);
std::cout << "main ptr:"<<ptr<<std::endl;
fun(ptr);
std::cout << "main exit"<<std::endl;
return 0;
}
需要牢记:new 或者 malloc 出来的内存, 需要去释放!
最后一问
网上经常说重载new,说的是什么,重载的又是什么?人们口中的new又是什么。
一次性解决模糊不清的概念。 重载new 本质是指的是重载operator new,或者叫new 操作符,或者操作符new.
经常提到的new 一个对象,指的是表达式new.
表达式new 和操作符 new的关系是包含关系,operator new是 new expression的工作子集。