美文网首页
The C++ standard library(侯捷/孟岩 译

The C++ standard library(侯捷/孟岩 译

作者: Ewitter | 来源:发表于2019-04-09 23:36 被阅读0次

    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<<std::hex<<3.4<<std::endl;
    2.使用using declaration,如此以下程序不必写出修饰符号std::,而可直接使用cout和endl:
        using std::cout;
        using std::endl;
        于是先前的例子可写为:cout<<std::hex<<3.4<<endl;
    3.使用using directive,这是最简便的选择,若对namespace std采用using directive,
        便可让std内的所有标识符有效(曝光),就像他们声明为全局标识符一样。
          因此,加入语句:using namespace std;前述例子可写入为:cout<<hex<<3.4<<endl;
    

    基本类型的显示初始化(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<int ,float> p;  //initialize p.first and p.second with zero
    即以int() 和float()来初始化p,这两个constructor都返回零值。
    

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

    structure pair定义于<utility>:

    namespace std
    {
        template <class T1,class T2>
        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<class U,class V>
            pair(const pair<U,V>& p):fisrt(p.first),second(p.second){}
        };
        //comparisons
        template <class T1,class T2>
        bool operator==(const pair<T1,T2>& ,const pair<T1,T2>&);
    
        template <class T1,class T2>
        bool operator< (const pair<T1,T2>& ,const pair<T1,T2>&);
    
        //... similar: !=, <= ,> ,>=
    
        //convenience function to create a pair
        template <class T1,class T2>
        pair<T1,T2> 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 T>
    class MyClass
    {
        public:
            //copy constructor with implicit type conversion
            //- does not hide implicit copy constructor
            template <class U>
            MyClass(const MyClass<U>& x);
            //...
    };
    void f()
    {
        MyClass<double> xd;
        //...
        MyClass<double> xd2(xd);  //calls built-in copy
        MyClass<int> 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<class T1,class T2>
        bool operator==(const pair<T1,T2>& x,const pair<T1,T2>& y)
        {
            return x.first == y.first && x.second == y.second;
        }
    }
    

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

    namespace std
    {
        template <class T1,class T2>
        bool operator< (const pair<T1,T2>& x,const pair<T1,T2>& y)  
        {
            return x.first<y.first || (!(y.first<x.first) && x.second<y.second);
        }
    }
    

    2.2 make_pair() (page36)

    template函数make_pair()使得无需写出型别就可以生成一个pair对象。
    code:
    
    namespace std
    {
        //create value pair only providing the value
        template <class T1,class T2>
        pair<T1,T2> make_pair(const T1& x,const T2& y)
        {
            return pair<T1,T2>(x,y);
        }
    }
    
    如此可用std::make_pair(42,'@'); 而不必费力写成 std::pair<int,char>(42,'@');
    

    注:std::pair<int,float>(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<classA> ptr1(new classA);  //OK
    std::auto_ptr<classA> 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<ClassA>  ptr1(new ClassA);
    //copy the auto_ptr,transfers  ownership from ptr1 to ptr2
    std::auto_ptr<ClassA> 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<ClassA> ptr1(new ClassA);
    std::auto<ClassA> 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<ClassA> ptr;  //create an auto_ptr
    ptr = new ClassA;  //error
    ptr = std::auto_ptr<ClassA> (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<ClassA>);  //sink() gets ownership
    
    2.某函数是数据的起点。
        当一个auto_ptr被返回,其拥有权便被转交给调用端了,eg:
    std::auto_ptr<ClassA> f()
    {
        std::auto_ptr<ClassA> ptr(new ClassA);  //ptr owns the new object
        //...
        return ptr;  //transfer ownership to calling function
    }
    void g()
    {
        std::auto_ptr<ClassA> 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 <class T>
    void bad_print(std::auto_ptr<T> 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<int> f()
    {
        const std::auto_ptr<int> p(new int);  //no ownership transfer possible
        std::auto_ptr<int> 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<ClassA> ptr1;  //auto_ptr members
        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 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 <iostream>
    #include <memory>
    using namespace std;
    
    /* define output operator for auto_ptr
    * print object value or nullptr
    */
    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()
    {
        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;   //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 <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
    }
    

    程序运行结果:

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

    3.7 auto_ptr source code (page56)

    class auto_ptr声明于 <memory>,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<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()
            {
                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<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());
            }
        };
    }
    

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

    相关文章

      网友评论

          本文标题:The C++ standard library(侯捷/孟岩 译

          本文链接:https://www.haomeiwen.com/subject/ocrriqtx.html