The C++ standard library(侯捷/孟岩 译) 01--pair/auto_ptr

1 隐式的return 0;(page21)

C/C++中可以使用return来结束main(),但与C不同的是,
C++在main()的末尾定义了一个隐式的return语句(即:return 0;)。

命名空间namespace(page23)

namespace,指标识符的某种可见范围。
使用C++标准程序库的任何标识符时,有3种选择:
1. 直接指定标志符:eg:std::ostream而不是ostream。
             完整语句类似这样:std:cout<

基本类型的显示初始化(explicit initialization)(page14)

如果采用不含参数的、明确的constructor调用方法,基本类型会被初始化为0:
int i1;  //undefined value
int i2 = int ();  //initialized with zero

2 pairs(对组)(page33)

class pair可将两个值视为一个单元,C++标准程序库中多处使用这个class,尤其map和multimap就是使用pairs来管理其键值(key/value)对元素,任何元素需返回两个值,也需要pair。

eg:std::pair p;  //initialize p.first and p.second with zero
即以int() 和float()来初始化p,这两个constructor都返回零值。

note:pair被定义为struct而不是class,如此所有成员都是public,故个直接存取pair的个别值。

structure pair定义于:

namespace std
{
    template 
    struct pair
    {
        //type name  for the values
        typedef T1 first_type;
        typedef T2 second_type;

        //member
        T1 first;
        T2 second;

        /*default constructor
        * T1() and T2() force initialization for built-in types
        */
        pair():first(T1()),second(T2()){}

        //constructor for two values
        piar(const T1& a,const T2& b):first(a),second(b){}

        //copy constructor with implicit conversions
        template
        pair(const pair& p):fisrt(p.first),second(p.second){}
    };
    //comparisons
    template 
    bool operator==(const pair& ,const pair&);

    template 
    bool operator< (const pair& ,const pair&);

    //... similar: !=, <= ,> ,>=

    //convenience function to create a pair
    template 
    pair make_pair(const T1&, const T2& );
}

template形式的构造函数并不会遮掩(由编译器)隐式生成的default构造函数(详见page13,如下):

template constructor是member template的一种特殊形式。
template constructor通常用于"在赋值对象时实现隐式类型转换"。
注意:template constructor并不遮掩(hide)implicit copy constructor。
如果类型完全吻合,implicit copy constructor会被产生出来并被调用,eg:

template 
class MyClass
{
    public:
        //copy constructor with implicit type conversion
        //- does not hide implicit copy constructor
        template 
        MyClass(const MyClass& x);
        //...
};
void f()
{
    MyClass xd;
    //...
    MyClass xd2(xd);  //calls built-in copy
    MyClass xi(xd);  //calls template constructor
}

上述代码中由于 xd2和xd的类型完全一致,所以它被內建的copy ctor初始化;
    xi的型别和xd的不同,所以它使用template ctor进行初始化。
故撰写template ctor时,如果default copy ctor不符合需要,
    则可以自己提供一个copy ctor。
2.1 pair之间的比较
若两个pair对象内所有元素都相等,则视两个pair对象相等。

namespace std
{
    template
    bool operator==(const pair& x,const pair& y)
    {
        return x.first == y.first && x.second == y.second;
    }
}

note:两个pair比较时,第一元素具有较高优先级,即两个pair的第一元素不等时,其比较结果就成为整个比较行为的结果,如果首元素等,才继续比较第二元素,并把比较结果作为整体比较结果:
code:(其它比较操作符类同)

namespace std
{
    template 
    bool operator< (const pair& x,const pair& y)  
    {
        return x.first

2.2 make_pair() (page36)

template函数make_pair()使得无需写出型别就可以生成一个pair对象。
code:

namespace std
{
    //create value pair only providing the value
    template 
    pair make_pair(const T1& x,const T2& y)
    {
        return pair(x,y);
    }
}

如此可用std::make_pair(42,'@'); 而不必费力写成 std::pair(42,'@');

注:std::pair(42,7.77)与std::make_pair(42,7.77)的结果不同,后者生成的pair的第二元素型别是double。(因为无任何饰词的浮点字面常数,其型别被视为double)

3 class auto_ptr(page38)

auto_ptr是一种智能型指针,帮助程序员防止“被异常抛出时发生资源泄露”。

函数的操作经常依以下模式进行:
1.获取一些资源-->2.执行一些操作-->3.释放所获取的资源

auto_ptr指针 是“它所指对象”的拥有者,当auto_ptr被摧毁时,其所指对象也将被摧毁。auto_ptr要求一个对象只能有一个拥有者,不能一物二主

auto_ptr<>不允许使用一般指针惯用的赋值(assign)初始化方式,必须直接使用数值来完成初始化,eg:

std::auto_ptr ptr1(new classA);  //OK
std::auto_ptr ptr1 = new classA;  //error

3.1 auto_ptr拥有权(ownership)的转移

由于不能出现多个auto_ptr同时拥有同一个对象的情况,
    但当用同一个对象初始化两个auto_ptr时会出现这种错误。解决办法是:
令auto_ptr的copy构造函数和assignment操作符 将对象拥有权交出去,

code:
//initialize an auto_ptr with a new object
std::auto_ptr  ptr1(new ClassA);
//copy the auto_ptr,transfers  ownership from ptr1 to ptr2
std::auto_ptr ptr2(ptr1);

上述代码ptr1拥有了new出来的对象的拥有权,第二个语句中将对象拥有权由 ptr1转交给ptr2,
  此后ptr2就拥有了那个new出来对象的拥有权且ptr1不再拥有该对象的拥有权,
  如此对象只会被delete一次——ptr2被销毁时。

assignment类同(当ptr2被赋值前拥有另一个对象,赋值动作发生时会调用delete将该对象删除):
//initialize an auto_ptr with a new object
std::auto_ptr ptr1(new ClassA);
std::auto ptr2;  //create another auto_ptr
ptr2 = ptr1;  //assign the auto_ptr ,
       //(and if an object owned by ptr2,delete object owned by ptr2 then) 
       //transfers ownership from ptr1 to ptr2

拥有者失去拥有权,便只剩一个null指针在手,且只能用auto_ptr指针来初始化另一个auto_ptr。

std::auto_ptr ptr;  //create an auto_ptr
ptr = new ClassA;  //error
ptr = std::auto_ptr (new ClassA);  //OK,delete old object and own new

3.2 source and sink(起点和终点)(page42)

某个函数可以利用auto_ptr将拥有权转交给另一个函数,情形如下两种:

1.某函数是数据的终点。
    若auto_ptr以by value(传值)方式当做参数传递给某函数,
      此时被调用端的参数获得了这个auto_ptr的拥有权,
      若函数不再将拥有权传递出去,则其所指对象会在函数退出时被删除。
void sink(std::auto_ptr);  //sink() gets ownership

2.某函数是数据的起点。
    当一个auto_ptr被返回,其拥有权便被转交给调用端了,eg:
std::auto_ptr f()
{
    std::auto_ptr ptr(new ClassA);  //ptr owns the new object
    //...
    return ptr;  //transfer ownership to calling function
}
void g()
{
    std::auto_ptr p;
    for (int i=0;i<10; ++i)
    {
        p=f();  //p gets ownership of the returned object 
                  //(previously returned object of f() gets deleted)
        //...
    }
}  //last-owned object of p gets deleted

每当f()被调用都会new一个新对象,并把该对象连同拥有权一起返回给调用端。
    即将返回值赋值给p,同时也完成了拥有权的转移。
    一旦循环再次执行该赋值操作,p原先拥有的对象将被删除。离开g()时p也会被销毁。

3.3 缺陷(page43)

auto_ptr语义本身包含拥有权,若无意转交拥有权则不要在参数中使用auto_ptr,eg:

//this is a bad example
template 
void bad_print(std::auto_ptr p)  //p gets ownership of passed argument
{
    //does p own an object ?
    if (p.get() ==NULL)
    {
        std::cout<<"NULL";
    }
    else
    {
        std::cout<<*p;
    }
}  //Oops,existing deletes the object to which p refers,见拥有权转移发生情况1

也不建议将auto_ptr以reference方式传参,用constant reference传参也很危险(但可通过某些是做技巧降低危险性)。(原因后续有了解到再添加)
总而言之,常数型auto_ptr减小了“不经意转移拥有权”所致的危险。只要一个对象通过auto_ptr传递,就可使用常数型auto_ptr来终结拥有权的转移,此后拥有权不能再进行转移。
此处,关键词const并非意味着不能更改auto_ptr所拥有的对象,而意味着不能更改auto_ptr的拥有权,eg:

std::auto_ptr f()
{
    const std::auto_ptr p(new int);  //no ownership transfer possible
    std::auto_ptr q(new int);  //ownership transfer possible

    *p = 42;  //OK,change value to which p refers
    *p = *q;  //OK,change value to which p refers
    p = q;  //compile-time error
    return  p;  //compile-time error
}

如果使用const auto_ptr作为参数,对新对象的任何赋值操作都将导致编译期错误。就常数特性而言,const auto_ptr比较类似常数指针(T* const p)而非 指向常数的指针(const T* p)。

3.4 auto_ptr作为成员之一(page44)

只有当对象被完整构造成功才有可能将来调用其析构函数,如此可能第一个new成功而第二个new失败了,就会造成资源遗失,eg:

class ClassB
{
private:
    ClassA* ptr1;  //pointer members
    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(vla2)){}
    
    //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可避免上述代码的问题(当对象被删除时,auto_ptr会自动删除其所指对象,故不需要析构函数)

class ClassB
{
private:
    const std::auto_ptr ptr1;  //auto_ptr members
    const std::auto_ptr  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 ctor,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 necessay
    //(default destructor lets ptr1 and ptr2 delete their objects)
    //...
}

3.5 auto_ptr的错误运用(page46)

为了正确使用auto_ptr,给出一些要点(note :第二点):

1. auto_ptr之间不能共享拥有权。
      eg:当第一个指针删除对象后,第二个指针指向了一个已被销毁的对象,
          那使用第二个指针进行读写时会导致不可预料的后果。

2. 并不存在针对array而设计的auto_ptrs。
      auto_ptr不能指向array,因为auto_ptr通过delete而非delete[]来释放所拥有的对象。
      而且,C++标准程序库未提供针对array设计的auto_ptr。
        标准程序库另提供了数个容器类别,用来管理数据群。

3. auto_ptrs不是一个“四海通用”的智能型指针。
      auto_ptr不是引用计数(reference counting)型指针。
      引用计数型指针保证:若有一组智能型指针指向同一个对象,
                        那当且仅当最后一个智能型指针被销毁时,该对象才会被销毁。

4. auto_ptrs 不满足STL容器对其元素的要求。
      因为copy和assignment操作后,
         原auto_ptr和新产生的auto_ptr不相等——copy和assign后原auto_ptr会交出拥有权,
          而不是拷贝给新的auto_ptr。故不要讲auto_ptr作为容器的元素。
          (程序库的设计本身就可以防止这种误用,这类误用无法通过编译)

3.6 auto_ptr实例(page47)

下例展示 auto_ptr转移拥有权的行为:

// util/autoptr1.cpp
#include 
#include 
using namespace std;

/* define output operator for auto_ptr
* print object value or nullptr
*/
template 
ostream& operator<< (ostream& strm,const auto_ptr& 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 p(new int(42));
    auto_ptr 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;   //change value of the object q owns
    p = q;
    cout << "after change and reassignment:" << endl;
    cout << " p: " << p << endl;
    cout << " q: " << q << endl;
}

程序运行结果:

autoptr1.png

谨记:auto_ptr只能用auto_ptr来初始化。(因为根据一般指针生成一个auto_ptr的那个构造函数被声明为explicit)

下例展示 const auto_ptr的特性:

// util/autoptr2.cpp

#include 
#include 
using namespace std;

/* define output operator for auto_ptr
* - print object value or NULL
*/
template 
ostream& operator<< (ostream& strm ,const auto_ptr& 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 p(new int(42));
    const auto_ptr q(new int(0));
    const auto_ptr 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
}

程序运行结果:

autoptr2.png

前面有说不应以任何形式传递auto_ptr,但此处是个例外
注: *r = *p; 是错误的。因为 对于一个“未指向任何对象”的auto_ptr进行提领(dereference)操作,C++标准规格会导致未定义行为(eg程序的崩溃)。即使r不具常数性,但p具有常数性,其拥有权不得被更改。

3.7 auto_ptr source code (page56)

class auto_ptr声明于 ,auto_ptr定义于 namespace std中,是“可用于任何型别”的一个template class,下面是auto_ptr的确切声明:

// util/autoptr.hpp

/* class auto_ptr
* - improved standard conforming implementation
*/
namespace std
{
    //auxiliary type to enable copies and assignments (now global)
    template
    struct auto_ptr_ref
    {
        Y* yp;
        auto_ptr_ref (Y* rhs) : yp(rhs){}
    };

    template
    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 auto_ptr (auto_ptr& 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 auto_ptr& operator= (auto_ptr& 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()
        {
            retuan 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 rhs) throw() : ap(rhs.yp) {}
        auto_ptr& operator= (auto_ptr_ref rhs) throw()   //new
        {
            reset(rhs.yp);
            return *this;
        }
        template operator auto_ptr_ref() throw()
        {
            return auto_ptr_ref(release());
        }
        template operator auto_ptr() throw()
        {
            return auto_ptr(release());
        }
    };
}

上述代码中引进auto_ptr_ref类别是为了协助将右值转化为左值,这一机制的理论基础是“重载”和“template参数推导规则”之间的一个细微的不同之处。

你可能感兴趣的:(The C++ standard library(侯捷/孟岩 译) 01--pair/auto_ptr)