引用:windows程序员面试指南
人为的new和delete操作动态内存,容易出现两种问题:
1.忘记释放内存,会造成内存泄漏;
2.尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。
auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。
当对象过期时,其析构函数将使用delete 来释放内存!
使用方法:
1.两个指针不能指向同一个资源:复制或赋值都会改变资源的所有权
2.重载了 * 和 -> ,像普通指针一样操作
3.get() // 智能指针托管的指针地址
auto_ptr<Test> test(new Test);
Test *tmp = test.get(); // 获取指针返回
// get() 实现
_NODISCARD _Ty * get() const noexcept
{ // return wrapped pointer
return (_Myptr);
}
4.release() // 取消智能指针对动态内存的托管
auto_ptr<Test> test(new Test);
Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管
delete tmp2; // 之前分配的内存需要自己手动释放
// release() 实现
_Ty * release() noexcept
{ // return wrapped pointer and give up ownership
_Ty * _Tmp = _Myptr;
_Myptr = nullptr;
return (_Tmp);
}
5.reset() // 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构
auto_ptr<Test> test(new Test);
test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL
test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之
// reset() 实现
void reset(_Ty * _Ptr = nullptr)
{ // destroy designated object and store new pointer
if (_Ptr != _Myptr)
delete _Myptr;
_Myptr = _Ptr;
}
弃用原因
1.复制或者赋值都会改变资源的所有权
// auto_ptr 被C++11抛弃的主要原因
auto_ptr<string> p1(new string("I'm Li Ming!"));
auto_ptr<string> p2(new string("I'm age 22."));
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
// p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针,
// 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。
p1 = p2;
cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl; // p1 为 p2 的地址
cout << "p2:" << p2.get() << endl; // p2 为 0x00000000
2.在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值
vector<auto_ptr<string>> vec;
auto_ptr<string> p3(new string("I'm P3"));
auto_ptr<string> p4(new string("I'm P4"));
// 必须使用std::move修饰成右值,才可以进行插入容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
// 风险来了:
vec[0] = vec[1]; // 如果进行赋值,问题又回到了上面一个问题中。
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl; // 被释放,访问越界
3.不支持对象数组的内存管理
auto_ptr<int[]> array(new int[5]); // 不能这样定义
c++11,替代了C++98的auto_ptr
使用方法
1.对象独有权:两个unique_ptr不能指向同一个对象
2.无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
unique_ptr<string> p1(new string("I'm Dangwei!"));
unique_ptr<string> p2(new string("I'm age 100."));
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
p1 = p2; // 禁止左值赋值
unique_ptr<string> p3(p2); // 禁止左值赋值构造
unique_ptr<string> p3(std::move(p1)); // 使用移动语义
p1 = std::move(p2); // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样
cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
3.保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象
4.在容器中保存指针需要使用move,可读性高易理解
vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
vec[0] = vec[1]; /* 不允许直接赋值 */
vec[0] = std::move(vec[1]); // 需要使用move修饰,使得程序员知道后果
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl; // 非法访问
5.支持对象数组
// 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]); // 支持这样定义
6.get(), release(), reset() 和 auto_ptr 一直
使用陷阱
auto_ptr 与 unique_ptr智能指针的内存管理陷阱
auto_ptr<string> p1;
string *str = new string("智能指针的内存管理陷阱");
p1.reset(str); // p1托管str指针
{
auto_ptr<string> p2;
p2.reset(str); // p2接管str指针时,会先取消p1的托管,然后再对str的托管
}
// 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错!
cout << "str:" << *p1 << endl;
// 使用 shared_ptr 来解决该问题
通过引用技术实现共享指针
构造
shared_ptr<int> up1(new int(10)); // int(10) 的引用计数为1
shared_ptr<int> up2(up1); // 使用智能指针up1构造up2, 此时int(10) 引用计数为2
初始化
shared_ptr<int> up3 = make_shared<int>(2); // 多个参数以逗号','隔开,最多接受十个
shared_ptr<string> up4 = make_shared<string>("字符串");
shared_ptr<Person> up5 = make_shared<Person>(9);
赋值
shared_ptrr<int> up1(new int(10)); // int(10) 的引用计数为1
shared_ptr<int> up2(new int(11)); // int(11) 的引用计数为1
up1 = up2; // int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2
主动释放对象
shared_ptrr<int> up1(new int(10));
up1 = nullptr ; // int(10) 的引用计数减1,计数归零内存释放
up1 = NULL; // 作用同上
重置
p.reset() ; 将p重置为空指针,所管理对象引用计数 减1
p.reset(p1); 将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
p.reset(p1,d); 将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器
交换
std::swap(p1,p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变
p1.swap(p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变
使用陷阱:线程安全问题
shared_ptr 包含两个成员变量,一个是指向变量的指针,一个是资源被引用的次数,其中:
1.引用次数加减操作内部自动加锁解锁,是线程安全的
2.指向对象的指针不是线程安全的
智能指针的赋值拷贝,首先拷贝指向对象的指针,再使引用次数加减操作,虽然引用次数加减是原子操作,
但是指针拷贝和引用次数两步操作 并不是原子操作,线程不安全,需要手动加锁解锁
使用陷阱:循环引用
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!
譬如:Boy类中有Girl的智能指针;Girl类中有Boy的智能指针;当他们交叉互相持有对方的管理对象时…
class Girl;
class Boy {
public:
Boy() {
cout << "Boy 构造函数" << endl;
}
~Boy() {
cout << "~Boy 析构函数" << endl;
}
void setGirlFriend(shared_ptr<Girl> _girlFriend) {
this->girlFriend = _girlFriend;
}
private:
shared_ptr<Girl> girlFriend;
};
class Girl {
public:
Girl() {
cout << "Girl 构造函数" << endl;
}
~Girl() {
cout << "~Girl 析构函数" << endl;
}
void setBoyFriend(shared_ptr<Boy> _boyFriend) {
this->boyFriend = _boyFriend;
}
private:
shared_ptr<Boy> boyFriend;
};
void useTrap() {
shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());
// 陷阱用法
spBoy->setGirlFriend(spGirl);
spGirl->setBoyFriend(spBoy);
// 此时boy和girl的引用计数都是2
}
int main(void) {
useTrap();
system("pause");
return 0;
}
// 当我们执行useTrap函数时,注意,是没有结束此函数,boy和girl指针其实是被两个智能指针托管的,所以他们的引用计数是2
// useTrap函数结束后,函数中定义的智能指针被清掉,boy和girl指针的引用计数减1,还剩下1,对象中的智能指针还是托管他们的
// 所以函数结束后没有将boy和gilr指针释放的原因就是于此
// 所以在使用shared_ptr智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!
为了解决该交叉引用的问题,可以使用 weak_ptr
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针,来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
1.weak_ptr 没有重载*和->,但可以使用 lock 获得一个可用的 shared_ptr 对象
shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());
// 弱指针的使用
weak_ptr<Girl> wpGirl_1; // 定义空的弱指针
weak_ptr<Girl> wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针
cout << "spGirl \t use_count = " << spGirl.use_count() << endl;
cout << "wpGirl_1 \t use_count = " << wpGirl_1.use_count() << endl;
// 弱指针不支持 * 和 -> 对指针的访问
/*wpGirl_1->setBoyFriend(spBoy);
(*wpGirl_1).setBoyFriend(spBoy);*/
// 在必要的使用可以转换成共享指针
shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();
cout << sp_girl.use_count() << endl;
// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;
继续刚才的例子,注意BOY类
class Girl;
class Boy {
public:
Boy() {
cout << "Boy 构造函数" << endl;
}
~Boy() {
cout << "~Boy 析构函数" << endl;
}
void setGirlFriend(shared_ptr<Girl> _girlFriend) {
this->girlFriend = _girlFriend;
// 在必要的使用可以转换成共享指针
shared_ptr<Girl> sp_girl;
sp_girl = this->girlFriend.lock();
cout << sp_girl.use_count() << endl;
// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;
}
private:
weak_ptr<Girl> girlFriend;
};
class Girl {
public:
Girl() {
cout << "Girl 构造函数" << endl;
}
~Girl() {
cout << "~Girl 析构函数" << endl;
}
void setBoyFriend(shared_ptr<Boy> _boyFriend) {
this->boyFriend = _boyFriend;
}
private:
shared_ptr<Boy> boyFriend;
};
void useTrap() {
shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());
spBoy->setGirlFriend(spGirl);
spGirl->setBoyFriend(spBoy);
}
int main(void) {
useTrap();
system("pause");
return 0;
// 在类中使用弱指针接管共享指针,在需要使用时就转换成共享指针去使用即可!
}
1.不要把一个原生指针给多个智能指针管理;
int *x = new int(10);
unique_ptr< int > up1(x);
unique_ptr< int > up2(x);
// 警告! 以上代码使up1 up2指向同一个内存,非常危险
或以下形式:
up1.reset(x);
up2.reset(x);
2.记得使用u.release()的返回值;
在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了.
3.禁止delete 智能指针get 函数返回的指针;
如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!
4.禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
shared_ptr< int > sp1(new int(10));
shared_ptr< int > sp4(sp1.get()); // 一个典型的错误用法
参考:https://zhuanlan.zhihu.com/p/526147194