《C++标准程序库》第四章摘录与笔记
4.1 Pairs(对组)
类pair可以将两个对象视为一个对象。容器类别map和multimap使用pairs来管理器键/值对。任何函数需返回两个值,也需要pair。4.2 Class auto_ptr
C++标准程序库提供的auto_ptr是“一种”智能指针,帮助程序员防止“被异常抛出时发生资源泄露”。auto_ptr只是针对特定问题而设计的,对于其他问题,他无能为力。4.2.1 auto_ptr的设计动机
对于对象指针,如果对象的资源是以显示方法获得,而且没有被绑定在任何对象上,那就必须以显示方法释放。但如果在释放前return或者发生异常,则可能内存泄露或者说资源遗失。一种方法是可以通过捕获所有异常来防止这种资源遗失,如:void f()
{
ClassA *ptr = new ClassA; // create an object explicitly
try {
... // perform some operations
}
catch(...) { // for any exception
delete ptr; // clean up
throw; // rethrow the exception
}
delete ptr; // clean up on normal end
}
这样处理显得复杂且容易出错。
另一种方法是使用智能指针。auto_ptr这种职能指针保证,无论在任何情况下,只要自己被摧毁,就一定连带释放其所指资源。而由于智能指针本身就是区域变量,所以无论是正常退出,还是异常退出,只要函数退出,它就一定会被摧毁。auto_ptr是“它所指对象”的拥有者,所以当auto_ptr被摧毁时,该对象也将遭到摧毁。auto_ptr要求一个对象只能有一个拥有者,严禁一物二主。(绝对不能使用同一个对象初始化多于一个的auto_ptr!!!)
《MSDN》摘录
The template class describes an object that stores a pointer to an allocated object of type Type* that ensures that the object to which it points gets destroyed automatically when control leaves a block.
The template class describes an object that stores a pointer to an allocated object myptr of type Type *. The stored pointer must either be null or designate an object allocated by a new expression. An object constructed with a nonnull pointer owns the pointer. It transfers ownership if its stored value is assigned to another object. (It replaces the stored value after a transfer with a null pointer.) The destructor for auto_ptr<Type> deletes the allocated object if it owns it. Hence, an object of class auto_ptr<Type> ensures that an allocated object is automatically deleted when control leaves a block, even through a thrown exception. You should not construct two auto_ptr<Type> objects that own the same object.
You can pass an auto_ptr<Type> object by value as an argument to a function call. You can return such an object by value as well. Both operations depend on the implicit construction of intermediate objects of class auto_ptr<Type>::auto_ptr_ref<Other>, by various subtle conversion rules. You cannot, however, reliably manage a sequence of auto_ptr<Type> objects with a Standard Template Library container.
注意,auto_ptr<>不允许你使用一般指针惯用的赋值初始化方式,你必须直接使用数值来完成初始:
std::auto_ptr<ClassA> ptr1(new ClassA); // OK
std::auto_ptr<ClassA> ptr2 = new ClassA; // ERROR!
4.2.2 auto_ptr拥有权的转移
auto_ptr的拷贝构造函数和赋值操作符的作用不是讲此处的数据拷贝到别处,而是进行了auto_ptr拥有权的转移!这样先前的拥有者是去了拥有权,只有null指针在手了,结果拷贝构造函数和赋值操作都带有副作用(对源对象进行了修改),这就要求程序员在之后不能再使用已经交出了拥有权的auto_ptr了。只有auto_ptr可以拿来当做另一个auto_ptr的初值,普通指针不行(这是因为“根据一般指针生成一个auto_ptr”的那个构造函数被声明为explicit了):
std::auto_ptr<ClassA> ptr; // create an auto_ptr
ptr = new ClassA; // ERROR
ptr = std::auto_ptr<ClassA>(new ClassA); // OK, delete old object and own new
起点和终站
拥有权的转移,使得auto_ptr产生了一种特殊用法:某个函数可以利用auto_ptr将拥有权转交给另一个函数。这种事情可能在两种情况出现:
1. 某个函数是数据的终点。如果auto_ptr以值传递方式被当做一个参数传递给另外一个函数。
2. 某个函数式数据的起点。当一个auto_ptr被返回,其拥有权便被转交给调用端了。
缺陷
auto_ptr的语义本身就包含了拥有权,所以如果你无意转交你的拥有权,就不要在参数列中使用auto_ptr,也不要以它作为返回值。
可以使用constant reference来使用auto_ptr来使其无法交出拥有权,这样会导致转移所有权的操作(如拷贝构造函数和赋值操作)在交出拥有权时出现编译错误。总而言之,常量型auto_ptr减小了“不经意转移拥有权”所带来的危险。只要一个对象通过auto_ptr传递,就可以使用常量型auto_ptr来终结拥有权转移链,此后拥有权将不能再进行转移。这里的const并非意味你不能更改auto_ptr所拥有的对象,而是意味你不能更改auto_ptr的拥有权,与指针常量类似。
const std::auto_ptr<int> p(new int); // no ownership transfer possible
如果使用const auto_ptr作为参数,对新对象的任何赋值操作都将导致编译器错误。
1. auto_ptrs直接不能共享拥有权
2. 并不存在针对array而设计的auto_ptrs。auto_ptr不可以指向array,因为auto_ptr是透过delete而非delete[]来释放其所拥有的对象。
3. auto_ptrs绝非一个“四海通用”的智能型指针。特别注意的是,它不是引用计数型指针!(第一条就可以发现了)“引用计数型智能指针”,在我们有必要在不同容器之间共享元素时非常有用!
4. auto_ptrs不满足STL容器对其元素的要求。还是由于拥有权移交时的副作用。所以请绝对不要将auto_ptr作为标准容器的元素!
const std::auto_ptr<int> p(new int); // no ownership transfer possible
如果使用const auto_ptr作为参数,对新对象的任何赋值操作都将导致编译器错误。
4.2.3 auto_ptr作为成员之一
在class中使用auto_ptr,你可以因而避免遗失资源。如果你以auto_ptr而非一般指针作为成员,当对象被删除时,auto_ptr会自动删除其所指的成员对象,于是你也就不再需要析构函数了。此外,即使在初始化期间抛出异常,auto_ptr也可以帮助避免资源遗失。注意,只有当对象被完整构造成功,才有可能于将来调用其析构函数。这就造成了资源遗失的隐忧:如果第一个new成功了,第二个new却失败了,就造成了资源遗失。如:class ClassB
{
private:
ClassA *ptr1; // pointer member
ClassA *ptr2;
public:
// constructor that initializes the pointers, will cause resource leak if second new throws
ClassB(ClassA val1, ClassA val2)
: ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
}
// copy constructor, might cause resource leak if second new throws
ClassB(const ClassB& x)
: ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
}
// assignment operator
const ClassB& operator=(const ClassB& x)
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
~ClassB()
{
delete ptr1;
delete ptr2;
}
// ...
};
使用auto_ptr则可以避免资源遗失:
class ClassB
{
private:
const std::auto_ptr<ClassA> ptr1; // auto_ptr member
const std::auto_ptr<ClassA> ptr2;
public:
// constructor that initializes the auto_ptrs, no resource leak possible
ClassB(ClassA val1, ClassA val2)
: ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
}
// copy constructor, no resource leak possible
ClassB(const ClassB& x)
: ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
}
// assignment operator
const ClassB& operator=(const ClassB& x)
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
// no destructor necessary
// (default destructor let ptr1 and ptr2 delete their objects)
// ...
};
但是要注意,由于缺省情况下,拷贝构造函数和赋值操作会导致auto_ptr转移拥有权,所以需要自己实现这两个成员函数。为了避免拥有权的转交,如果你的auto_ptr在整个生命周期中不必改变其所指对象的拥有权,你可以使用const auto_ptr。
4.2.4 auto_ptr的错误运用
C++标准程序库中只有auto_ptr这一个智能指针。使用它的一些要点:1. auto_ptrs直接不能共享拥有权
2. 并不存在针对array而设计的auto_ptrs。auto_ptr不可以指向array,因为auto_ptr是透过delete而非delete[]来释放其所拥有的对象。
3. auto_ptrs绝非一个“四海通用”的智能型指针。特别注意的是,它不是引用计数型指针!(第一条就可以发现了)“引用计数型智能指针”,在我们有必要在不同容器之间共享元素时非常有用!
4. auto_ptrs不满足STL容器对其元素的要求。还是由于拥有权移交时的副作用。所以请绝对不要将auto_ptr作为标准容器的元素!
4.2.5 auto_ptr运用实例
#include <iostream>
#include <memory>
using namespace std;
// define output operator for auto_ptr
// print object value or NULL
// 第二个参数是一个const reference,所以没有发生auto_ptr拥有权的转移!
template <typename 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()
{
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 << "agter assigning auto pointers: " << endl;
cout << "p: " << p << endl;
cout << "q: " << q << endl;
*q += 13; // change value of the object q owns
p = q;
cout << "after change and reassignment: " << endl;
cout << "p: " << p << endl;
cout << "q: " << q << endl;
return 0;
}
![](https://img-my.csdn.net/uploads/201207/28/1343468886_6616.jpg)
#include <iostream>
#include <memory>
using namespace std;
// define output operator for auto_ptr
// print object value or NULL
// 第二个参数是一个const reference,所以没有发生auto_ptr拥有权的转移!
template <typename 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(runtime error)
*p = -77;
cout << "agter 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
return 0;
}
![](https://img-my.csdn.net/uploads/201207/28/1343468862_6518.jpg)
4.2.6 auto_ptr实作细目
/**
* class auto_ptr
* -- improved standard conforming implementation
*/
// 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
{
public:
// constructor
explicit auto_ptr(T* p = 0);
// copy constructor (with implicit conversion)
// member template
// 以任何兼容的auto_ptr作为一个新的auto_ptr的初值
// --note: nonconstant parameter
template <class U>
auto_ptr(auto_ptr<U>& rhs);
// destructor
~auto_ptr();
// assignment operator (with implicit conversion)
// member template
// 以任何兼容的auto_ptr作为赋值动作的右端
// --note: nonconstant parameter
template <class U>
auto_ptr<T>& operator=(auto_ptr<U>& rhs);
// value access
T& operator*() const;
T* operator->() const;
T* get() const; // 返回dumb pointer
// release ownership
T* release(); // 撤回dumb pointer的拥有权并返回其值
// reset value
void reset(T* p = 0); // 将拥有的指针删除,并承担p的拥有权
// special conversions to enable copies and assignments
public:
auto_ptr(auto_ptr_ref<T> rhs);
auto_ptr<T>& operator=(auto_ptr_ref<T> rhs);
template <class U> operator auto_ptr_ref<U>();
template <class U> operator auto_ptr<U>();
private:
T *pointee; // refers to the actual owned object(if any)
// 让所有的auto_ptr classes都成为另一个auto_ptr的friends
// template <class U>
// friend class auto_ptr<U>;
};
template <class T>
inline auto_ptr<T>::auto_ptr(T* p)
: pointee(p)
{}
template <class T>
template <class U>
inline auto_ptr<T>::auto_ptr(auto_ptr<U>& rhs)
: pointee(rhs.release())
{}
template <class T>
inline auto_ptr<T>::~auto_ptr()
{
delete pointee;
}
template <class T>
template <class U>
inline auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<U>& rhs)
{
if (this != &rhs)
reset(rhs.release());
return *this;
}
template <class T>
inline T& auto_ptr<T>::operator*() const
{
return *pointee;
}
template <class T>
inline T* auto_ptr<T>::operator->() const
{
return pointee;
}
template <class T>
inline T* auto_ptr<T>::get() const
{
return pointee;
}
template <class T>
inline T* auto_ptr<T>::release()
{
T *oldPointee = pointee;
pointee = 0;
return oldPointee;
}
template <class T>
inline void auto_ptr<T>::reset(T *p)
{
if (pointee != p)
{
delete pointee;
pointee = p;
}
}
// special conversion with auxiliary type to enable copies and assignments
template <class T>
inline auto_ptr<T>::auto_ptr(auto_ptr_ref<T> rhs)
: pointee(rhs.yp)
{}
template <class T>
inline auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr_ref<T> rhs)
{
reset(rhs.yp);
return *this;
}
template <class T>
template <class U>
inline auto_ptr<T>::operator auto_ptr_ref<U>()
{
return auto_ptr_ref<U>(release());
}
template <class T>
template <class U>
inline auto_ptr<T>::operator auto_ptr<U>()
{
return auto_ptr<U>(release());
}
参考了《More Effective C++》
在实现auto_ptr中,拷贝构造函数和赋值操作符参数都使用的是reference object而不是reference to const object。这是由于auto_ptr需要在拷贝时放弃拥有权,并且拷贝动作必须修改原始auto_ptr。而要修改原始auto_ptr,如果使用reference to const object,则auto_ptr中实际包含的指针必须声明为mutalbe,但是这样有会让用户可以那些声明为const的对象,将其所有权交给别人。所以将其设置为reference object。
#include <iostream>
#include "auto_ptr.h"
int main()
{
auto_ptr<int> pi(new int(0));
auto_ptr<char> pc(new char(49));
const auto_ptr<int> cpi(new int(11));
auto_ptr<int> api(new int(22));
*pi = 10;
// api.reset(pi.get()); // 在VS2005中运行时出现了错误,但是在GCC中没出现错误。
std::cout << *pi << std::endl;
std::cout << *pc << std::endl;
std::cout << *cpi << std::endl;
std::cout << *api << std::endl;
return 0;
}
注释的这行在VS2005中都会导致运行时错误,因为出现了两个指针指向一个对象,这样释放两次就会出错,但是GCC中测试没有出错,单独在GCC中写一段测试代码释放掉同一动态申请的区域则会出错。