目录
写在前面
总览
智能指针的分类
为什么要使用智能指针?
auto_ptr
致谢
在过去几天中,我深入研究了智能指针的相关知识。这已经不是我第一次学习智能指针了,但这一次,我感觉自己真正理解了智能指针的工作原理。最初我打算在完整掌握智能指针的所有细节后再进行分享,但随着学习的深入,我发现内容远比预想的要丰富。因此,我决定边学习边总结,边回顾边分享。今天,我想和大家讨论一个已被淘汰的智能指针——auto_ptr
。我们将从它的使用方法、内部源码分析、存在的缺陷,到常见的陷阱几个方面进行深入探讨,以彻底把握智能指针的核心逻辑。这不仅为我们后续学习 shared_ptr
、unique_ptr
以及 weak_ptr
打下了坚实的基础,而且通过理解其需求和不足,我们能更加深刻地体会智能指针设计背后的理念,使得我们在使用它们时更加得心应手。
后续的智能指针今天也会一并上传,大家共勉~
auto_ptr
unique_ptr
shared_ptr
weak_ptr
举一个特别容易范的错误,当开辟一个堆空间来存储变量,却忘记了释放,这种可能大家觉得很容易注意到。
#include
#include
#include
int memory_leak_demo(){
auto* str = new std::string("Too tired, go home and have rest~");
std::cout << *str << std::endl;
//forgot to free the memory
return 0;
}
那么就是另外一个例子,明明特别注意了堆空间的释放,但是碰到了异常抛出?
这里稍微注意一点,在g++上异常的抛出不能用std::exception("string....")
的形式,因为没有这个构造函数(visual studio 上面是可以的)。这时我们应该使用std::runtime_error("string...")
来实现异常的抛出。
int memory_leak_demo_2(){
auto* str = new std::string("There is some trap, which is hard to identify~");
std::cout << *str << std::endl;
{
//this operation is not allowed in g++ or clang++,
//while allowed in Microsoft visual studio
// throw std::exception("something_wrong");共勉
//this is the way used in g++
throw std::runtime_error("something wrong");
}
//due to the exception function, the following codes will not be executed
//as a result, the free operation will not be executed, which is dangerous
std::cout << *str << std::endl;
delete str;
return 0;
}
然后大家可以做一个简单的测试来查看一下我们内存的占用情况,给你们一个dirty的代码:
void test01(){
for(int i=0; i<100000; ++i){
for(int j=0; j<100000; ++j){
memory_leak_demo();
try{
memory_leak_demo_2();
}catch(std::runtime_error& e){
std::cout << e.what() << std::endl;
}
}
}
}
int main() {
test01();
return 0;
}
如果内存小的话应该是可以很快占满的,博主的电脑128gb
等了一会实在等不下去了,但是确实能看到内存再不断上升,这里就不贴运行结果了。
那么这个问题怎么解决呢?其实标准库中已经帮我们实现了,那就是直接使用std::string
对象,在程序结束,或者被异常终止时,std::string
的析构函数就会被调用。
#include
#include
#include
//better way
int memory_leak_demo_opt(){
std::string str("Too tired, go home and have rest~");
std::cout << str << std::endl;
//the memory will be free automatically,
// even in the std::string definition, the memory was allocated in heap
return 0;
}
int memory_leak_demo_2_opt(){
std::string str("There is some trap, which is hard to identify~");
std::cout << str << std::endl;
{
//the destructor of std::string will free the memory allocated for the string
throw std::runtime_error("something wrong");
}
//due to the exception function, the following codes will not be executed
//however, the free operation will be executed automatically, which is called by the destructor
std::cout << str << std::endl;
return 0;
}
然后重复一下上面的工作,做一个测试函数吧:
void test02(){
for(int i=0; i<100000; ++i){
for(int j=0; j<100000; ++j){
memory_leak_demo_opt();
try{
memory_leak_demo_2_opt();
}catch(std::runtime_error& e){
std::cout << e.what() << std::endl;
}
}
}
}
int main() {
test02();
return 0;
}
这一次大家可以观察一下,内存实际上没有之前操作的那种持续的上升了,这也就印证了我们的假设,析构函数是可以实现内存的自动释放的,即使是遇到了异常的抛出,这一点我们实际上在之前的excption
专题中专门用例子查看过。
此外,其实我们知道std::string
的内部实现也是通过开辟和释放堆内存的方式来实现的,那么我们是否可以参考这种实现的方法来对我们的指针进行优化呢?
这个问题的普遍性和重要性大家应该感受到了,而且貌似我们也已经找到了问题的解决办法,即实现一个配有析构函数的指针实现自动释放开辟的堆内存。
cpp标准库就是这么想得,于是便有了cpp98
中的第一个smart pointer auto_ptr
。
auto_ptr
是cpp98
定义的智能指针模板,其内部定义了管理指针的机制,可以将new 获得的地址直接赋值给这个对象,在对象的生命周期截止时会自动调用析构函数来释放相应的内存。
转入auto_ptr
的定义我们可以看到其成员变量,构造函数(constructor)以及析构函数(destructor),其实这个智能指针就是对一个指向一段在堆上开辟的内存的指针_M_ptr
进行管理的类:
template
class auto_ptr
{
private:
_Tp* _M_ptr;
public:
/// The pointed-to type.
typedef _Tp element_type;
/**
* @brief An %auto_ptr is usually constructed from a raw pointer.
* @param __p A pointer (defaults to NULL).
*
* This object now @e owns the object pointed to by @a __p.
*/
explicit
auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
~auto_ptr() { delete _M_ptr; }
//other members.............
};
使用方法
头文件引入#include
使用方法auto_ptr
例如:
#include
#include
#include
int main(){
std::auto_ptr pInt(new int(5));
std::auto_ptr pString(new std::string("hello world"));
std::auto_ptr> pVint(new std::vector(10));
return 0;
}
这里有一个小问题,如果直接用博主的主机运行这段代码,会报很多警告,但是不会影响你运行代码。例如:
====================[ Build | project03 | Debug ]===============================
/home/herryao/Software/clion-2023.2/bin/cmake/linux/x64/bin/cmake --build /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/cmake-build-debug --target project03 -- -j 10
[ 50%] Building CXX object CMakeFiles/project03.dir/main.cpp.o
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/main.cpp: In function ‘void memory_leak_demo_opt_pro()’:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/main.cpp:29:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
29 | std::auto_ptr t0(new Test{});
| ^~~~~~~~
In file included from /usr/include/c++/12/memory:75,
from /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/main.cpp:1:
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/main.cpp: In function ‘int main()’:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/main.cpp:33:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
33 | std::auto_ptr pInt(new int(5));
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/main.cpp:34:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
34 | std::auto_ptr pString(new std::string("hello world"));
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/main.cpp:35:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
35 | std::auto_ptr> pVint(new std::vector(10));
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
[100%] Linking CXX executable project03
[100%] Built target project03
Build finished
大概意思就是这个东西被弃用了,最好使用std::unique_ptr
,这里先买个关子,一会会讲为什么。
我们来用一个小例子对这个被废弃的智能指针做一个练习:
首先总体介绍一下auto_ptr
的使用方法:
首先就是operator * 和 operator ->
这两个操作符号的重载直接使得其使用方法和普通指针相同,其原型实现如下:
public:
/// The pointed-to type.
typedef _Tp element_type;
element_type&
operator*() const throw()
{
__glibcxx_assert(_M_ptr != 0);
return *_M_ptr;
}
/**
* @brief Smart pointer dereferencing.
*
* This returns the pointer itself, which the language then will
* automatically cause to be dereferenced.
*/
element_type*
operator->() const throw()
{
__glibcxx_assert(_M_ptr != 0);
return _M_ptr;
}
.get()
:直接返回相应的指针,类似于直接使用智能指针,其原型实现如下:
public:
element_type*
get() const throw() { return _M_ptr; }
.release()
: 对当前管控的内存托管,会返回管控的内存,此时相当于智能指针失效,这之后堆内存就需要手动释放了。其原型实现如下:
public:
/// The pointed-to type.
typedef _Tp element_type;
element_type*
release() throw()
{
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
.reset()
: 重置当前管理的内存,如果传入的地址和原内存不一致,那就会对原受控内存直接进行析构,然后接管传入的内存,也就是两个智能指针之间所有权的转移。其原型实现如下:
public:
void
reset(element_type* __p = 0) throw()
{
if (__p != _M_ptr)
{
delete _M_ptr;
_M_ptr = __p;
}
}
拷贝构造函数(copy constructor)中定义的拷贝类似于所有权的交接,调用了被拷贝对象的release()
方法,这一点很反直觉,类似地还有拷贝赋值(copy assignment)即operator =
的重写,稍后会继续解释这个问题。其定义如下:
public:
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }
几点注意事项:
不要把auto_ptr
对象声明为全局变量,这样等同于没有意义。
千万慎用赋值,这是cpp11
弃用auto_ptr
的最主要原因。下面是拷贝赋值的源码实现,其操作类似于拷贝构造,可以看到实际上就是一个所有权的转移,调用了之前提到的reset()
方法。这种方法很隐蔽,交接所有权后会把原来的autoptr
中管理的内存置空,如果再次访问这个指针,会造成未定义行为,即访问空指针。
尽管 std::auto_ptr
的赋值操作实际上执行的是所有权的转移,这种设计被认为是不直观的,容易导致错误,特别是在复杂的程序中。这是因为它违反了赋值操作通常不改变源对象的常规预期。
public:
template
auto_ptr&
operator=(auto_ptr<_Tp1>& __a) throw()
{
reset(__a.release());
return *this;
}
不要创建一个指向(auto_ptr
对象)的指针,因为这样只会直接删除掉这个指针变量,而并不会调用其指向内容的析构函数,如此一来相当于没有意义,换句话来说,如果创建的是智能指针对象的指针,那么这个东西实际上就是一串地址而已,出栈销毁后对其指向对象没影响。
auto_ptr
在cpp11
后被废弃了,unique_ptr
完全可以取而代之。
下面用一个例子来将上述方法及注意事项逐个演练一下:
#include
#include
#include
#include
#include
//define one class to check the detail of the memory free operation
class Test{
public:
Test(){
std::cout << "Test is constructed" << std::endl;
this->_flag = 1;
}
~Test(){
std::cout << "Test is destructed" << std::endl;
}
[[nodiscard]]int getFlag() const{
return this->_flag;
}
protected:
int _flag;
};
//warning 1: dont declare the smart pointer as a global variable, which result in meaningless of smart pointer usage;
//std::auto_ptr t(new Test{});
void memory_leak_demo_opt_pro(){
std::auto_ptr t0(new Test{});
//warning 2: do not declare one pointer of smart pointer pointing at smart pointer object....
//this will not call the destructor of the smartpointer, but will directly delete the pointer pointing the smartpointer, which is useless
//std::auto_ptr* tp = new std::auto_ptr(new Test{});
//warning 3: unless fully understand the operation, try not to assign one auto_ptr object to another
//when checking the source code of auto_ptr, it can be seen that the copy assignment is reset operation,
// which means giving the ownership to another auto_ptr, while current controlling memory will be set
// as nullptr, if this is ignored and try to further access the memory inside the operated auto_ptr, resulting
// in a nullptr accessing, meaning one undefined operation
// std::auto_ptr t1 = t0;
//The usage of smart pointer is same as the normal pointer object, since the operator* and operator-> are overloaded
std::cout << "calling from the -> operator " << t0->getFlag() << std::endl;
std::cout << "calling from the . operator " << (*t0).getFlag() << std::endl;
//the get() method
Test* temp = t0.get();
std::cout << "access the method with .get() member function: " << temp->getFlag() << std::endl;
//the release() method
//release() method will give up the control authority and return the pointer
//after this operation, the smart pointer is deactivated, and the memory in heap need to be free manually
Test* targ = t0.release();
std::cout << "access the method using the return object from .release() method: " << targ->getFlag() << std::endl;
//the reset() method
//to reset the memory that the auto_ptr pointing at, if with different memory, the previous memory will be free.
//initialize a new object to show the method
std::auto_ptr t2(new Test{});
//equal to delete the object
//t2.reset();
t2.reset(new Test{});
};
int main(){
// std::auto_ptr pInt(new int(5));
// std::auto_ptr pString(new std::string("hello world"));
// std::auto_ptr> pVint(new std::vector(10));
memory_leak_demo_opt_pro();
return 0;
}
运行结果如下,基本的测试都已通过,大家可以结合源码来理解下这个输出,其实就是分别测试了各种的方法并得到一致的运行结果:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project03/cmake-build-debug/project03
Test is constructed
calling from the -> operator 1
calling from the . operator 1
access the method with .get() member function: 1
access the method using the return object from .release() method: 1
Test is constructed
Test is constructed
Test is destructed
Test is destructed
Process finished with exit code 0
而再此我想说的是我注释掉的部分,std::auto_ptr
,这么一句话会导致t0被置空,当在此访问t0时会造成未定义行为。
我在多做一个测试来验证一下这个过程,定义两个管理string
的auto_ptr
对象,然后我们对其进行赋值拷贝操作,并通过.get()
方法来查看在这个操作前后所管理的内存空间:
#include
#include
#include
void trap_01() {
std::auto_ptr p1(new std::string("this is asif"));
std::auto_ptr p2(new std::string("this is gulgun"));
std::cout << "=====================before assignment=====================" << std::endl;
std::cout << "address of p1: " << p1.get() << std::endl;
std::cout << "address of p2: " << p2.get() << std::endl;
p1 = p2;
std::cout << "=====================after assignment=====================" << std::endl;
std::cout << "address of p1: " << p1.get() << std::endl;
std::cout << "address of p2: " << p2.get() << std::endl;
}
int main() {
trap_01();
return 0;
}
其输出结果如下,可以看出在运行拷贝赋值之后,如我们之前所说的,他将第一个管理的内容删掉了,然后对传入的对象进行了release()
操作置为了空,可以看到操作后p1指向了p2管理的内存而p2指向了nullptr
。
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/cmake-build-debug/project01
=====================before assignment=====================
address of p1: 0x55ce8af35eb0
address of p2: 0x55ce8af35ee0
=====================after assignment=====================
address of p1: 0x55ce8af35ee0
address of p2: 0
Process finished with exit code 0
auto_ptr的陷阱一:
我们定义两个auto_ptr
的空构造对象,然后让其同时指向一个被创建在堆空间的指针。
#include
#include
#include
void trap_02(){
std::auto_ptr p1{};
std::auto_ptr p2{};
std::string* str = new std::string("hello world");
std::cout << "=============================== before reset() ===============================" << std::endl;
std::cout << "address of p1: " << p1.get() << std::endl;
std::cout << "address of p2: " << p2.get() << std::endl;
p1.reset(str);
p2.reset(str);
std::cout << "=============================== after reset() ===============================" << std::endl;
std::cout << "address of p1: " << p1.get() << std::endl;
std::cout << "address of p2: " << p2.get() << std::endl;
}
int main() {
trap_02();
return 0;
}
其输出结果如下所示,可以看出两个auto_ptr
同时指向了相同的一段内存空间,这时如果程序结束调用二者的析构会发生什么呢?会对已经释放的内存进行多次释放造成段错误,这很危险且编译器无法识别。
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/cmake-build-debug/project01
=============================== before reset() ===============================
address of p1: 0
address of p2: 0
=============================== after reset() ===============================
address of p1: 0x5589aded1eb0
address of p2: 0x5589aded1eb0
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
换一种方式来重新看待这个操作过程,假如我们的程序一直在运行,但是运行期间我们对其中一个auto_ptr
进行了释放,之后我们尝试访问另外一个同指向这个内存空间的对象会怎么样呢?
#include
#include
#include
void trap_03(){
std::auto_ptr p1{};
std::string* str = new std::string("hello world");
std::cout << "address of p1: " << p1.get() << std::endl;
p1.reset(str);
{
std::auto_ptr p2{};
std::cout << "address of p2: " << p2.get() << std::endl;
p2.reset(str);
std::cout << "address of p2: " << p2.get() << std::endl;
}
std::cout << "=============================== after p2 delete ===============================" << std::endl;
std::cout << "address of p1: " << p1.get() << std::endl;
std::cout << "=============================== try to access the content managed in p1===============================" << std::endl;
std::cout << *p1.get() << std::endl;
}
int main() {
trap_03();
return 0;
}
输出结果如下,可以看出,如果在程序运行过程中,auto_ptr
的使用会造成安全隐患,可能会终止程序运行。
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/cmake-build-debug/project01
address of p1: 0
address of p2: 0
address of p2: 0x55cb0947eeb0
=============================== after p2 delete ===============================
address of p1: 0x55cb0947eeb0
=============================== try to access the content managed in p1===============================
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
陷阱二与标准库容器的联用:
下面我们依旧定义两个auto_ptr
然后尝试将其存储到std::vector
容器中:
#include
#include
#include
#include
void trap_04(){
std::auto_ptr p1(new std::string("this is asif"));
std::auto_ptr p2(new std::string("this is gulgun"));
std::vector> v1;
v1.push_back(p1);
v1.push_back(p2);
// v1.push_back(std::move(p1));
// v1.push_back(std::move(p2));
}
int main() {
trap_04();
return 0;
}
输出结果如下,大致意思就是编译器知道auto_ptr
的拷贝构造会破坏原始对象的所有权,我后面的两个把智能指针当作右值传入是可行的(这种操作也是所有的智能指针新标准中要求的操作方法,转移所有权需要用右值转换,在这个操作后原始的对象会失去价值,并被容器接管):
====================[ Build | project01 | Debug ]===============================
/home/herryao/Software/clion-2023.2/bin/cmake/linux/x64/bin/cmake --build /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/cmake-build-debug --target project01 -- -j 10
[ 50%] Building CXX object CMakeFiles/project01.dir/main.cpp.o
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp: In function ‘void trap_01()’:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:7:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
7 | std::auto_ptr p1(new std::string("this is asif"));
| ^~~~~~~~
In file included from /usr/include/c++/12/memory:75,
from /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:2:
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:8:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
8 | std::auto_ptr p2(new std::string("this is gulgun"));
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp: In function ‘void trap_02()’:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:20:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
20 | std::auto_ptr p1{};
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:21:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
21 | std::auto_ptr p2{};
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp: In function ‘void trap_03()’:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:35:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
35 | std::auto_ptr p1{};
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:42:14: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
42 | std::auto_ptr p2{};
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp: In function ‘void trap_04()’:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:54:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
54 | std::auto_ptr p1(new std::string("this is asif"));
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:55:10: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
55 | std::auto_ptr p2(new std::string("this is gulgun"));
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:56:22: warning: ‘template class std::auto_ptr’ is deprecated: use 'std::unique_ptr' instead [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wdeprecated-declarations•-Wdeprecated-declarations•]8;;•]
56 | std::vector> v1;
| ^~~~~~~~
/usr/include/c++/12/bits/unique_ptr.h:64:28: note: declared here
64 | template class auto_ptr;
| ^~~~~~~~
In file included from /usr/include/x86_64-linux-gnu/c++/12/bits/c++allocator.h:33,
from /usr/include/c++/12/bits/allocator.h:46,
from /usr/include/c++/12/string:41,
from /usr/include/c++/12/bits/locale_classes.h:40,
from /usr/include/c++/12/bits/ios_base.h:41,
from /usr/include/c++/12/ios:42,
from /usr/include/c++/12/ostream:38,
from /usr/include/c++/12/iostream:39,
from /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:1:
/usr/include/c++/12/bits/new_allocator.h: In instantiation of ‘void std::__new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::auto_ptr >; _Args = {const std::auto_ptr, std::allocator > >&}; _Tp = std::auto_ptr >]’:
/usr/include/c++/12/bits/alloc_traits.h:516:17: required from ‘static void std::allocator_traits >::construct(allocator_type&, _Up*, _Args&& ...) [with _Up = std::auto_ptr >; _Args = {const std::auto_ptr, std::allocator > >&}; _Tp = std::auto_ptr >; allocator_type = std::allocator > >]’
/usr/include/c++/12/bits/stl_vector.h:1281:30: required from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::auto_ptr >; _Alloc = std::allocator > >; value_type = std::auto_ptr >]’
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/main.cpp:57:17: required from here
/usr/include/c++/12/bits/new_allocator.h:175:11: error: no matching function for call to ‘std::auto_ptr >::auto_ptr(const std::auto_ptr >&)’
175 | { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/12/memory:81:
/usr/include/c++/12/backward/auto_ptr.h:127:9: note: candidate: ‘template std::auto_ptr< >::auto_ptr(std::auto_ptr<_Up>&) [with _Tp = std::__cxx11::basic_string]’
127 | auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }
| ^~~~~~~~
/usr/include/c++/12/backward/auto_ptr.h:127:9: note: template argument deduction/substitution failed:
/usr/include/c++/12/bits/new_allocator.h:175:11: note: types ‘std::auto_ptr<_Up>’ and ‘const std::auto_ptr >’ have incompatible cv-qualifiers
175 | { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/12/backward/auto_ptr.h:266:7: note: candidate: ‘std::auto_ptr< >::auto_ptr(std::auto_ptr_ref<_Tp>) [with _Tp = std::__cxx11::basic_string]’
266 | auto_ptr(auto_ptr_ref __ref) throw()
| ^~~~~~~~
/usr/include/c++/12/backward/auto_ptr.h:266:43: note: no known conversion for argument 1 from ‘const std::auto_ptr >’ to ‘std::auto_ptr_ref >’
266 | auto_ptr(auto_ptr_ref __ref) throw()
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
/usr/include/c++/12/backward/auto_ptr.h:114:7: note: candidate: ‘std::auto_ptr< >::auto_ptr(std::auto_ptr< >&) [with _Tp = std::__cxx11::basic_string]’ (near match)
114 | auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }
| ^~~~~~~~
/usr/include/c++/12/backward/auto_ptr.h:114:7: note: conversion of argument 1 would be ill-formed:
/usr/include/c++/12/bits/new_allocator.h:175:11: error: binding reference of type ‘std::auto_ptr >&’ to ‘const std::auto_ptr >’ discards qualifiers
175 | { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/12/backward/auto_ptr.h:105:7: note: candidate: ‘std::auto_ptr< >::auto_ptr(element_type*) [with _Tp = std::__cxx11::basic_string; element_type = std::__cxx11::basic_string]’
105 | auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
| ^~~~~~~~
/usr/include/c++/12/backward/auto_ptr.h:105:30: note: no known conversion for argument 1 from ‘const std::auto_ptr >’ to ‘std::auto_ptr >::element_type*’ {aka ‘std::__cxx11::basic_string*’}
105 | auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
| ~~~~~~~~~~~~~~^~~~~~~
gmake[3]: *** [CMakeFiles/project01.dir/build.make:76: CMakeFiles/project01.dir/main.cpp.o] Error 1
gmake[2]: *** [CMakeFiles/Makefile2:83: CMakeFiles/project01.dir/all] Error 2
gmake[1]: *** [CMakeFiles/Makefile2:90: CMakeFiles/project01.dir/rule] Error 2
gmake: *** [Makefile:124: project01] Error 2
陷阱三:标准库容器中的拷贝操作
那么如果我们全部转为右值就真的解决了这个问题了吗?
我们继续尝试上面的操作,然后我们将第二个元素赋值给第一个元素(这是容器中很常见的操作,所有的元素都应该是可相互赋值的)并看看会发生什么:
#include
#include
#include
#include
void trap_05(){
std::auto_ptr p1(new std::string("this is asif"));
std::auto_ptr p2(new std::string("this is gulgun"));
std::vector> v1;
v1.push_back(std::move(p1));
v1.push_back(std::move(p2));
std::cout << "v1[0]:" << *v1[0] << std::endl;
std::cout << "v1[1]:" << *v1[1] << std::endl;
v1[0] = v1[1];
std::cout << "=============================== after assignment ===============================" << std::endl;
std::cout << "v1[0]:" << *v1[0] << std::endl;
std::cout << "v1[1]:" << *v1[1] << std::endl;
}
int main() {
trap_05();
return 0;
}
最终输出结果如下,可以看出又一次其内置拷贝构造函数的弊端被展示出来,一旦拷贝,就会导致原对象失效,如果再次访问这个对象,就会造成越界访问的未定义操作。(其实我们也看出来这个拷贝根本就不应该存在)
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Tue/project01/cmake-build-debug/project01
v1[0]:this is asif
v1[1]:this is gulgun
=============================== after assignment ===============================
v1[0]:this is gulgun
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
auto_ptr
不支持数组的管理,比如std::auto_ptr
是不被支持的。
我们来总结一下auto_ptr
的弊端,这样能让我们更好地理解为什么他会在cpp11
被废弃在cpp17
中被移除,以及为什么我们需要全新的智能指针:
所有权语义混淆:在进行赋值或者赋值时直接进行了所有权的交接并把被操作对象置空,这种方法不符合传统复制语义,即复制操作不改变原始被复制的内容,容易造成程序员混淆。
不适合在标准库中的容器操作:在容器中,比如std::vector
中在将一个元素push到其中会引发拷贝构造操作,但是对于auto_ptr
中的拷贝构造实际上会将原始对象置空,这显然不是容器的本身逻辑。
不支持数组类型指针的创建和管理:当 std::auto_ptr
被设计出来的时候,它主要用于管理单个对象而非对象数组。在其源码中,我们刚刚查看过其构造函数析构函数分别定义为new 和delete
而非new[]和delete[]
这就造成了其无法管理和创建数组类型的尴尬。
不支持多个所有者:std::auto_ptr
不支持多个指针共享对同一对象的所有权,这会导致程序的崩溃。这限制了它在某些场景下的使用,例如,不能用于实现共享资源的管理。
不支持多态和基类/派生类关系:auto_ptr
在处理基类和派生类之间的指针转换时可能会遇到问题。由于所有权的转移特性,将派生类的 auto_ptr
赋值给基类的 auto_ptr
可能导致对象切割(slicing)或其他问题。
在cpp11
中unique_ptr , shared_ptr以及 weak_ptr
三者的引入完美解决了以上提到的所有问题。
感谢Martin老师的课程。
感谢各位的支持,祝大家的cpp水平越来越强。