编程八股文——C/C++中智能指针性质和使用
编程八股文——C/C++中智能指针性质和使用
1 智能指针
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
C++ 11中最常用的智能指针类型为shared_ptr
,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。
- 该引用计数的内存在堆上分配。当新增一个时引用计数加 1 ,当过期时引用计数减一。只有引用计数为 0 时,智能指针才会自动释放引用的内存资源。
- 对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类,可以通过构造函数传入普通指针。( 从
auto_ptr
也带一点,虽然C++ 11已经遗弃)
std::auto_ptr<string> ptr(new string);
std::shared_ptr<string> p1;
std::shared_ptr<list<int>> p2;
auto_ptr<char*> ap(new char*);
*ap = "ap1";
*ap = "ap2";
char **bp = new char*;
*bp = "bp1";
cout << *ap << endl; // "ap2"
cout << *bp << endl; // "bp1"
#include <memory>
double *p = new double;
shared_ptr<double> pshared(p); // 合法,显示转换,explicit conversion
//shared_ptr<double> pshared = p; // 不合法,隐式转换,implicit conversion
int main(int argc, char* argv[]) {
string str("hello world!");
// 程序能运行,但是在要释放 pshared 指向的内存时会出错
// 因为 str 不是存在堆中,当 pshared 过期时,delete 运算符会用于非堆内存,造成错误
shared_ptr<string> pshared(&str);
cout << *pshared << endl;
getchar();
}
-
auto_ptr 是C++98提供的解决方案,C++11已经摒弃,并提供了以下几种方案
-
shared_ptr 被称为共享指针,用于管理多个智能指针共同拥有的动态分配对象,
-
unique_ptr 唯一拥有指定的对象,相比普通指针,拥有 RAII 的特性使得程序出现异常时,动态资源可以得到释放。
RAII,Resource Acquisition Is Initialization,资源获取即初始化: 其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源
-
weak_ptr 是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
2 智能指针的内存泄露以及解决方法
当两个对象相互使用一个 shared_ptr
成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
为了解决循环引用导致的内存泄漏,引入了 weak_ptr
弱指针,weak_ptr
的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。
3 为什么摒弃 auto_ptr
auto_ptr<string> p1(new string("hello"));
auto_ptr<string> p2;
p2 = p1;
// 当 p1, p2 过期时,将删除同一个对象两次
解决之道:
- 定义复制运算符,使之执行深复制
- 建立所有权概念,使同时只有一个智能指针可拥有它。这样,只有拥有对象的智能指针有权析构该对象,这是auto_ptr 的策略,unique_ptr 的策略更严格。
- 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。
int main(int argc, char* argv[]) {
auto_ptr<string> p1(new string("hello"));
auto_ptr<string> p2;
cout << *p1 << endl; // 正常打印
p2 = p1; // p1 丧失了对 string 对象的所有权,p1 此时是空指针
cout << *p1 << endl; // 编译通过,但运行时报错,因为试图提领空指针
getchar();
}
// 将 auto_ptr 换成 unique_ptr,编译器认为语句 p2 = p1; 非法,在编译阶段报错(因为 p1 不是临时右值)。
// 将 auto_ptr 换成 shared_ptr,编译运行阶段都没问题,正常打印。
// shared_ptr 采用的策略是引用计数,赋值时,计数加一,过期时,计数减一。仅当最后一个指针过期时,才调用 delete。
shared_ptr<string> p1(new string("hello"));
shared_ptr<string> p2(p1); // 合法,将右值 p1 赋给 p2
unique_ptr<string> p1(new string("hello"));
//shared_ptr<string> p2(p1); // 不合法,右值 p1 是 unique_ptr,若能赋给 p2,则 p1,p2 指向同一个对象,导致 p1 不合法,此语句编译不通过
//unique_ptr<string> p2(p1); // 不合法,p1 不是临时右值,注意临时。
unique_ptr<string> foo() {
unique_ptr<string> p1(new string("hello"));
return p1;
}
// 函数返回的 unique_ptr<string> 为临时右值,此时可赋给另一个 unique_ptr 型指针
int main(int argc, char* argv[]) {
unique_ptr<string> p2(foo());
}
4 C++ 11 智能指针更多用法
shared_ptr
和unique_ptr
都支持的操作:
shared_ptr
独有的操作:
make_shared
函数(定义在头文件memory中)在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr
。
5 智能指针与普通指针的转换
void show(string s)
{
cout << s << endl;
}
int main()
{
std::shared_ptr<std::string> s = std::make_shared<std::string>("hello\n");
show(*s.get()); // s.get() 获得内置指针 string *,需要解引用传到show()中
// 特别注意,不要使用get()初始化另一个智能指针或为智能指针赋值。
return 0;
}
shared_ptr<int> p(new int(42)); // reference count is 1
int *q = p.get(); // ok: but don't use q in any way that might delete its pointer
{ // new block
// undefined: two independent shared_ptrs point to the same memory
shared_ptr<int>(q);
} // block ends, q is destroyed, and the memory to which q points is freed
int foo = *p; // undefined; the memory to which p points was freed
智能指针的get
函数返回一个内置指针,指向智能指针管理的对象。主要用于向不能使用智能指针的代码传递内置指针。使用get
返回指针的代码不能delete
此指针。
不要使用get
初始化另一个智能指针或为智能指针赋值。
6 shared_ptr
shared_ptr是否线程安全
指针和引用计数是线程安全的,但指针所指对象中的操作就需要自己做控制,并不是线程安全的。因为shared_ptr 有两个数据成员(指向被管理对象的指针,和指向控制块的指针),读写操作不能原子化。使得多线程读写同一个 std::shared_ptr 对象需要加锁。
- 同一个shared_ptr被多个线程“读”是安全的。
- 同一个shared_ptr被多个线程“写”是不安全的。
- 共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的。
使用 std::shared_ptr管理buffer或者数组
class O
{
public:
O() : mValue(0){};
~O() { printf("call ~O(), mValue = %d\n", mValue); };
int mValue;
};
int main()
{
int bufferSize = 10;
auto pData = std::shared_ptr<uint8_t>(new uint8_t[bufferSize], [](uint8_t *ptr) {
std::cout << "delete[] buffer pData" << std::endl;
delete[] ptr;
});
auto sp = std::shared_ptr<O>(new O[5], [](O *ptr) { delete[] ptr; });
O *p = sp.get();
for (int i = 0; i < 5; ++i)
{
(p + i)->mValue = i;
}
}
/*
call ~O(), mValue = 4
call ~O(), mValue = 3
call ~O(), mValue = 2
call ~O(), mValue = 1
call ~O(), mValue = 0
delete[] buffer pData
*/
7 unique_ptr
与shared_ptr
不同,同一时刻只能有一个unique_ptr
指向给定的对象。当unique_ptr
被销毁时,它指向的对象也会被销毁。
make_unique
函数(C++14新增,定义在头文件memory中)在动态内存中分配一个对象并初始化它,返回指向此对象的unique_ptr
。
unique_ptr<int> p1(new int(42));
// C++14
unique_ptr<int> p2 = make_unique<int>(42);
由于unique_ptr
独占其指向的对象,因此unique_ptr
不支持普通的拷贝或赋值操作。
unique_ptr
操作:
release
函数返回unique_ptr
当前保存的指针并将其置为空。
reset
函数成员接受一个可选的指针参数,重新设置unique_ptr
保存的指针。如果unique_ptr
不为空,则它原来指向的对象会被释放。
// 将所有权从 p1 (which points to the string Stegosaurus) 转移给 p2
unique_ptr<string> p2(p1.release()); // release makes p1 null
unique_ptr<string> p3(new string("Trex"));
// transfers ownership from p3 to p2
p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed
调用release
会切断unique_ptr
和它原来管理的对象之间的联系。release
返回的指针通常被用来初始化另一个智能指针或给智能指针赋值。如果没有用另一个智能指针保存release
返回的指针,程序就要负责资源的释放。
p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer
auto p = p2.release(); // ok, but we must remember to delete(p)
不能拷贝unique_ptr
的规则有一个例外:可以拷贝或赋值一个即将被销毁的unique_ptr
(移动构造、移动赋值)。
unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int (p));
// . . .
return ret;
}
老版本的标准库包含了一个名为auto_ptr
的类,
类似shared_ptr
,默认情况下unique_ptr
用delete
释放其指向的对象。unique_ptr
的删除器同样可以重载,但unique_ptr
管理删除器的方式与shared_ptr
不同。定义unique_ptr
时必须在尖括号中提供删除器类型。创建或reset
这种unique_ptr
类型的对象时,必须提供一个指定类型的可调用对象(删除器)。
// p points to an object of type objT and uses an object of type delT to free that object
// it will call an object named fcn of type delT
unique_ptr<objT, delT> p (new objT, fcn);
void f(destination &d /* other needed parameters */)
{
connection c = connect(&d); // open the connection
// when p is destroyed, the connection will be closed
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
} // 当 p 被摧毁时,自动调用 end_connection 函数
8 weak_ptr
weak_ptr
是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr
管理的对象。将weak_ptr
绑定到shared_ptr
不会改变shared_ptr
的引用计数。如果shared_ptr
被销毁,即使有weak_ptr
指向对象,对象仍然有可能被释放。
创建一个weak_ptr
时,需要使用shared_ptr
来初始化它。weak_ptr
只能配合std::shared_ptr
使用,不能单独使用。
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp weakly shares with p; use count in p is unchanged
使用weak_ptr
访问对象时,必须先调用lock
函数。该函数检查weak_ptr
指向的对象是否仍然存在。如果存在,则返回指向共享对象的shared_ptr
,否则返回空指针。
if (shared_ptr<int> np = wp.lock())
{
// true if np is not null
// inside the if, np shares its object with p
}
使用weak_ptr防止循环引用
#include <memory>
#include <iostream>
class Foo : public std::enable_shared_from_this<Foo>
{
public:
Foo() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
~Foo() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
void self()
{
mPtr = shared_from_this();
}
private:
// std::shared_ptr<Foo> mPtr; // 【1】由于循环引用,不会调用析构函数,改 mPtr 为 std::weak_ptr 类型即可
std::weak_ptr<Foo> mPtr; //【2】
};
int main()
{
{
std::shared_ptr<Foo> c = std::make_shared<Foo>();
c->self();
}
return 0;
}
/**
注释【1】打开【2】,打印
Foo::Foo()
Foo::~Foo()
注释【2】打开【1】,打印
Foo::Foo()
由于循环引用,不会调用析构函数
*/
std::enable_shared_from_this<T>::shared_from_this
是个侵入式设计。为的解决传入this
导致对象被析构两次的问题。
什么情况下需要使用 shared_from_this()
? 用于返回当前对象 this
的std::shared_ptr
类型指针时:
#include <memory>
#include <iostream>
class Foo : public std::enable_shared_from_this<Foo>
{
public:
Foo() { std::cout << "Foo()\n"; }
~Foo() { std::cout << "~Foo()\n"; }
std::shared_ptr<Foo> getSelf()
{
return shared_from_this();
}
};
int main()
{
Foo *foo = new Foo;
std::shared_ptr<Foo> sp1(foo);
std::shared_ptr<Foo> sp2 = sp1->getSelf(); // 【1】为了对 foo对象进行共享
//std::shared_ptr<Foo> sp2(foo); // 【2】
// std::boolalpha 的作用是使 bool 型变量按照 false、true 的格式输出。如不使用该标识符,那么结果会按照 1、0 的格式输出
std::cout << std::boolalpha << (sp2.get() == foo) << std::endl;
std::cout << sp1.use_count() << " " << sp2.use_count() << std::endl;
}
/* 打印
Foo()
true
2 2
~Foo()
*/
如果注释【1】打开【2】,则会析构两次,产生未定义的行为,打印如下
Foo()
true
1 1
~Foo()
~Foo()
free(): double free detected in tcache 2
已放弃 (核心已转储)
尽管sp1
和sp2
都指向了foo
,但是却不共享计数,当析构的时候就会被析构两次,产生未定义行为。
std::weak_ptr
可以接受std::shared_ptr
参数来构造自己,std::shared_ptr
也具有接受std::weak_ptr
参数来构造自己。
enable_shared_from_this 函数原型
template<typename _Tp>
class enable_shared_from_this {
protected:
...
public:
shared_ptr<_Tp>
shared_from_this() {
return shared_ptr<_Tp>(this->_M_weak_this);
}
shared_ptr<const _Tp>
shared_from_this() const {
return shared_ptr<const _Tp>(this->_M_weak_this);
}
private:
...
mutable weak_ptr<_Tp> _M_weak_this;
}
enable_shared_from_this
的子类需要返回自身的std::shared_ptr
指针,那么就需要继承这个类。
成员变量为什么是weak_ptr
类型
因为如果是std::shared_ptr
类型,那么就永远无法析构对象自身。
这个_M_weak_this
不是这个类中初始化,而是在shared_ptr
中初始化,初始化的值就是this
。因此如果智能指针类型是std::shared_ptr
,那么这个类对象一旦创建,引用计数就是1,那么永远也无法析构。
为什么不直接传回this
std::shared_ptr
的引用计数增加是需要用operator=
实现的。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!