目錄
auto_ptr作爲成員之一的應用
使用auto_ptr的一個最關鍵理由,就是使用了auto_ptr可以避免遺失資源。使用auto_ptr作爲成員,當對象被刪除時,auto_ptr也會自動刪除其所指向的成員對象。
另外,在對象初始化的時候,auto_ptr也有其優勢,按照普遍的new和delete的做法,只有當對象通過new的方式創建成功了,纔可以進行後一步的delete,那如果在new的時候沒創建成功,但同時有真的發生了佔據內存的操作時,就無法析構佔據的空間了。造成了內存資源的泄漏。但是,使用auto_ptr進行創建的時候,在對象初始化期間如果拋出異常,auto_ptr也可以幫助避免資源的遺失,這是new和delete搭配所做不到的。
比如,下面是一個按照普遍的new和delete的做法,進行的一個函數設計:
class ClassB {
private:
ClassA* ptr1;
ClassA* ptr2;
public:
ClassB(ClassA vall, ClassA val2)
:ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) //構造函數:通過new,進行參數的初始化操作
{
}
ClassB(const ClassB& x)
:ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) //拷貝構造函數:依舊是通過new,進行參數的初始化操作
{
}
const ClassB& operator= (const ClassB& x) //賦值操作符的重載
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
~ClassB() {
delete ptr1;
delete ptr2;
}
};
這種設計方式帶來的問題,之前說過了,就是,如果第一個new成功了,但第二個new卻失敗了,就會造成資源的遺失。只有當所有的new都創建成功了纔可以,但凡有一個沒成功,那所有的就都斷了,已經創建的也無法再析構了。
再看使用auto_ptr來進行初始化,可以避免上面的問題:
class ClassB {
private:
const std::auto_ptr<ClassA> ptr1;
const std::auto_ptr<ClassB> ptr2;
public:
ClassB(ClassA val1, ClassA val2)
:ptr1(new ClassA(val1)), ptr2(new ClassA(val2))//構造函數,使用auto_ptr初始化
{}
ClassB(const ClassB& x)
:ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2))//拷貝構造函數,使用auto_ptr初始化
{}
const ClassB& operator= (const ClassB& x)//賦值的操作符的重載
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
};
但是,需要強調的是,即便auto_ptr可以實現不依靠析構函數來進行析構,但拷貝構造和賦值操作符重載也是需要進行重新編寫的。如果不重寫,那編譯器會自己添加,那麼,這兩個操作都會轉交擁有權,這恐怕就不一定是編程的人的意願了。所以,如果在整個生命週期內的auto_ptr都不想改變其所指向對象的擁有權。最好還是可以選擇const auto_ptr,來進行約束。
auto_ptr的錯誤運用
auto_ptr之間不能共享擁有權
一個auto_ptr千萬不能指向另一個auto_ptr(或其他對象)所擁有的對象。否則,當第一個指針刪除該對象後,另一個指針突然間指向了一個已被銷燬的對象,那麼,如果再使用那個指針進行讀寫操作,就會引起一場災難。
並不存在針對array而設計的auto_ptrs
auto_ptr不可以指向array,這是因爲auto_ptr的本質還是通過delete來進行刪除的,而不是delete[]來釋放其所擁有的對象的。而且,C++標準庫中並未提供針對array而設計的auto_ptr。當然,標準庫會另外提供一些容器來管理數據羣。
auto_ptrs絕非一個“四海通用”的智能型指針
並非是任何適合智能型指針的地方,都適合auto_ptr。特別提醒的是,如果是引用計數型的指針,如果有一組智能型指針指向同一個對象,那麼,當且僅當最後一個智能型指針被銷燬時,該對象纔會被銷燬。故這種場景下,並不適合用auto_ptr來指向。
auto_ptrs不滿足STL容器對其元素的要求
比如,在拷貝動作和賦值動作之後,原本的auto_ptr和新產生的auto_ptr並不相等。原本的auto_ptr會交出控制權,而不是普遍意義上的拷貝給新的auto_ptr,所以,這個地方的拷貝和賦值函數就需要重新的定義了。一般的適用於其他元素的拷貝和賦值操作,就不適合auto_ptr所指向的類型。
auto_ptrs的忠告
有時候使用指向一個非常數的auto_ptr,並不比使用一個一般指針更安全。就比如當遇到引用計數型的場景的時候,就反而非常糟糕。而當我們有必要在不同容器之間共享元素時,這種指針非常有用了就。
auto_ptr的運用實例
根據之前的記述,下面對auto_ptr進行一個實際的操作,深切的體會到auto_ptr類似於一個常量指針,允許修改其所指向的內容,但不能修改其指針變量本身,下面這個展現auto_ptrs的轉移所有權的行爲:
#include <iostream>
#include <memory>
using namespace std;
template <class T>
ostream& operator<< (ostream& strm, const auto_ptr<T>& p)
{
if (p.get() == NULL)
{
strm << "NULL";
}
else
{
strm << *p;
}
return strm;
}
int main()
{
auto_ptr<int> p(new int(42));
auto_ptr<int> q;
cout << "after initialization:" << endl;
cout << "p:" << p << endl;
cout << "q:" << q << endl;
q = p;
cout << "after assigning auto pointers:" << endl;
cout << "p:" << p << endl;
cout << "q:" << q << endl;
*q += 13;
p = q;
cout << "after change and reassignment:" << endl;
cout << "p:" << p << endl;
cout << "q:" << q << endl;
system("pause");
}
運行結果:
下面再看,展示const auto_ptr的特性的例子:
#include <iostream>
#include <memory>
using namespace std;
/* define output operator for auto_ptr
* - print object value or NULL
*/
template <class T>
ostream& operator<< (ostream& strm, const auto_ptr<T>& p)
{
// does p own an object ?
if (p.get() == NULL) {
strm << "NULL"; // NO: print NULL
}
else {
strm << *p; // YES: print the object
}
return strm;
}
int main()
{
const auto_ptr<int> p(new int(42));
const auto_ptr<int> q(new int(0));
const auto_ptr<int> r;
cout << "after initialization:" << endl;
cout << " p: " << p << endl;
cout << " q: " << q << endl;
cout << " r: " << r << endl;
*q = *p;
// *r = *p; // ERROR: undefined behavior
*p = -77;
cout << "after assigning values:" << endl;
cout << " p: " << p << endl;
cout << " q: " << q << endl;
cout << " r: " << r << endl;
// q = p; // ERROR at compile time
// r = p; // ERROR at compile time
system("pause");
}
運行結果:
auto_ptr的實作細目
這裏來記錄下auto_ptr的實現機理:
/* class auto_ptr
* - improved standard conforming implementation
*/
namespace std {
// auxiliary type to enable copies and assignments (now global)
template<class Y>
struct auto_ptr_ref {
Y* yp;
auto_ptr_ref(Y* rhs)
: yp(rhs) {
}
};
template<class T>
class auto_ptr {
private:
T* ap; // refers to the actual owned object (if any)
public:
typedef T element_type;
// constructor
explicit auto_ptr(T* ptr = 0) throw()
: ap(ptr) {
}
// copy constructors (with implicit conversion)
// - note: nonconstant parameter
auto_ptr(auto_ptr& rhs) throw()
: ap(rhs.release()) {
}
template<class Y>
auto_ptr(auto_ptr<Y>& rhs) throw()
: ap(rhs.release()) {
}
// assignments (with implicit conversion)
// - note: nonconstant parameter
auto_ptr& operator= (auto_ptr& rhs) throw() {
reset(rhs.release());
return *this;
}
template<class Y>
auto_ptr& operator= (auto_ptr<Y>& rhs) throw() {
reset(rhs.release());
return *this;
}
// destructor
~auto_ptr() throw() {
delete ap;
}
// value access
T* get() const throw() {
return ap;
}
T& operator*() const throw() {
return *ap;
}
T* operator->() const throw() {
return ap;
}
// release ownership
T* release() throw() {
T* tmp(ap);
ap = 0;
return tmp;
}
// reset value
void reset(T* ptr = 0) throw() {
if (ap != ptr) {
delete ap;
ap = ptr;
}
}
/* special conversions with auxiliary type to enable copies and assignments
*/
auto_ptr(auto_ptr_ref<T> rhs) throw()
: ap(rhs.yp) {
}
auto_ptr& operator= (auto_ptr_ref<T> rhs) throw() { // new
reset(rhs.yp);
return *this;
}
template<class Y>
operator auto_ptr_ref<Y>() throw() {
return auto_ptr_ref<Y>(release());
}
template<class Y>
operator auto_ptr<Y>() throw() {
return auto_ptr<Y>(release());
}
};
}