C++ Primer 中文版 学习笔记(十七)

第十八章 特殊工具与技术

18.1. 优化内存分配

  • new 基于每个对象分配内存的事实可能会对某些类强加不可接受的运行时开销,这样的类可能需要使用用户级的类类型对象分配能够更快一些。这样的类使用的通用策略是,预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象。如vector
  • 分配原始内存时,必须在该内存中构造对象;对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删除现存对象,如果没有现存对象,赋值操作符中的动作就会有灾难性效果。
  • C++ 提供下面两种方法分配和释放未构造的原始内存。

       1)allocator 类,它提供可感知类型的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。

       2)标准库中的 operator new 和 operator delete,它们分配和释放需要大小的原始的、未类型化的内存。

  • C++ 还提供不同的方法在原始内存中构造和撤销对象。

       1)allocator 类定义了名为 construct 和 destroy 的成员。construct成员在未构造内存中初始化对象,destroy 成员在对象上运行适当的析构函数。

       2) 定位 new 表达式接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。

       3)可以直接调用对象的析构函数来撤销对象。运行析构函数并不释放对象所在的内存。

       4)算法 uninitialized_fill 和 uninitialized_copy 像 fill 和 copy算法一样执行,除了它们的目的地构造对象而不是给对象赋值之外。

  • allocator 类是一个模板,allocator 类将内存分配和对象构造分开。当allocator 对象分配内存的时候,它分配适当大小并排列成保存给定类型对象的空间。但是,它分配的内存是未构造的,allocator 的用户必须分别construct 和 destroy 放置在该内存中的对象。

allocator<T> a; //定义名为 a 的 allocator 对象,可以分配内存或构造 T 类型的对象

a.allocate(n)//分配原始的未构造内存以保存 T 类型的 n 个对象

a.deallocate(p, n)//释放内存,在名为 p 的 T* 指针中包含的地址处保存 T 类型的 n 个对象。运行调用 deallocate 之前在该内存中构造的任意对象的destroy 是用户的责任

a.construct(p, t)//在 T* 指针 p 所指内存中构造一个新元素。运行 T 类型的复制构造函数用 t 初始化该对象

a.destroy(p)//运行 T* 指针 p 所指对象的析构函数

uninitialized_copy(b, e, b2)//从迭代器 b 和 e 指出的输入范围将元素复制到从迭代器 b2 开始的未构造的原始内存中。该函数在目的地构造元素,而不是给它们赋值。假定由 b2 指出的目的地足以保存输入范围中元素的副本

uninitialized_fill(b, e, t)//将由迭代器 b 和 e 指出的范围中的对象初始化为 t 的副本。假定该范围是未构造的原始内存。使用复制构造函数构造对象

uninitialized_fill_n(b, e, t, n)//将由迭代器 b 和 e 指出的范围中至多 n个对象初始化为 t 的副本。假定范围至少为 n 个元素大小。使用复制构造函数构造对象

 

// pseudo-implementation of memory allocation strategy for a vector-like class
     template <class T> class Vector {
     public:
         Vector(): elements(0), first_free(0), end(0) { }
         void push_back(const T&);
          // ...
     private:
         static std::allocator<T> alloc; // object to get raw memory
         void reallocate(); // get more space and copy existing elements
         T* elements;       // pointer to first element in the array
         T* first_free;     // pointer to first free element in the array
         T* end;            // pointer to one past the end of the array
         // ...
     };
template <class T> void Vector<T>::push_back(const T& t) {
         // are we out of space?
         if (first_free == end)
           reallocate(); // gets more space and copies existing elements to it
         alloc.construct(first_free, t); // new (first_free) T(t); 
         ++first_free;
     }
template <class T> void Vector<T>::reallocate() {
         // compute size of current array and allocate space for twice as many elements
         std::ptrdiff_t size = first_free - elements;
         std::ptrdiff_t newcapacity = 2 * max(size, 1);
         // allocate space to hold newcapacity number of elements of type T
         T* newelements = alloc.allocate(newcapacity);  //T* newelements = static_cast<T*> (operator new[](newcapacity * sizeof(T))); 
         // construct copies of the existing elements in the new space
         uninitialized_copy(elements, first_free, newelements);
         // destroy the old elements in reverse order
         for (T *p = first_free; p != elements; /* empty */ )
            alloc.destroy(—p);                     //  p->~T(); 
         // deallocate cannot be called on a 0 pointer
         if (elements)    //传给 deallocate 一个零指针是不合法的。
             // return the memory that held the elements
             alloc.deallocate(elements, end - elements); // operator delete[](elements); 
         // make our data structure point to the new elements
         elements = newelements;
         first_free = elements + size;
         end = elements + newcapacity;
     }
  • new表达式调用名为 operator new的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。

         delete表达式对指向的对象运行适当的析构函数;然后,通过调用名为operator delete 的标准库函数释放该对象所用内存。

        调用 operator delete 函数不会运行析构函数,它只释放指定的内存

  • 标准库函数operator new和operator delete是 allocator 的 allocate和deallocate成员的低级版本,它们都分配但不初始化内存.
  •      void *operator new(size_t);       // allocate an object
         void *operator new[](size_t);     // allocate an array
    
         void *operator delete(void*);     // free an object
         void *operator delete[](void*);   // free an array
    
  • allocator 的成员 construct 和 destroy 也有两个低级选择

       定位 new 表达式在已分配的原始内存中初始化一个对象,它不分配内存。比construct 更灵活,它可以使用任何构造函数。construct 函数总是使用复制构造函数。

      使用析构函数的显式调用作为调用 destroy 函数的低级选择

         new (place_address) type

         new (place_address) type (initializer-list)

     allocator<string> alloc;
     string *sp = alloc.allocate(2); // allocate space to hold 2 strings
     // two ways to construct a string from a pair of iterators
     new (sp) string(b, e);                    // construct directly in place
     alloc.construct(sp + 1, string(b, e));   // build and copy a temporary
  • 相对都使用低级版本而言,使用allocate类更好,因为operator new分配空间时要对指针强制类型转换,而且allocate类提供可感知类型的内存管理更安全灵活,但也要注意 定位new表达式比construct要灵活。
  • 编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员 new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。
  • 成员 new 和 delete 函数:如果类定义了这两个成员中的一个,它也应该定义另一个。

    这些函数隐式地为静态static函数.因为它们要么在构造对象之前使用operator new,要么在撤销对象之后使用(operator delete),

         void* operator new(size_t); //类成员 operator new必须返回类型void* 并接受 size_t 类型的形参。用以字节计算的分配内存量初始化函数的size_t 形参。

void operator delete(void*);//必须具有返回类型void。它可以定义为接受单个void*类型形参,也可以定义为接受两个形参

void operator delete(void*,size_t);//void*可以是空指针。若提供了size_t 形参,就由编译器用第一个形参所指对象的字节大小自动初始化size_t

当基类有virtual析构函数,size_t则传给 operator delete 的大小将根据被删除指针所指对象的动态类型而变化.

类成员 operator new[] 必须具有返回类型 void*,并且接受的第一个形参类型为 size_t。用表示存储特定类型给定数目元素的数组的字节数值自动初始化操作符的 size_t 形参。

成员操作符 operator delete[] 必须具有返回类型 void,并且第一个形参为 void* 类型。用表示数组存储起始位置的值自动初始化操作符的 void*形参。也可以有两个形参,第二个形参为 size_t。如果提供了附加形参,由编译器用数组所需存储量的字节数自动初始化这个形参。

 

  • 覆盖类特定的内存分配: 如果类定义了自己的成员 new 和 delete,类的用户就可以通过使用全局作用域确定操作符,用::new 或 ::delete 表达式使用全局的库函数。

          如果用 new 表达式调用全局 operator new 函数分配内存,则 delete 表达式也应该调用全局 operator delete 函数。

  •    一个内存分配器基类    

            改进内置库的 new 和 delete 函数。一个通用策略是预先分配一场原始内存来保存未构造的对象,创建新元素的时候,可以在一个预先分配的对象中构造;释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返还给系统。这种策略常被称为维持一个自由列表freelist。可以将自由列表实现为已分配但未构造的对象的链表。

template <class Type>
     class QueueItem: public CachedObj< QueueItem<Type> > {
          // remainder of class declaration and all member definitions unchanged
     };
template <class T> class CachedObj {
     public:
         void *operator new(std::size_t);
         void operator delete(void *, std::size_t);
         virtual ~CachedObj() { }
     protected:
         T *next;
     private:
         static void add_to_freelist(T*);
         static std::allocator<T> alloc_mem;
         static T *freeStore;
         static const std::size_t chunk;
     };
template <class T>
         void *CachedObj<T>::operator new(size_t sz)
         {
              // new should only be asked to build a T, not an object
              // derived from T; check that right size is requested
              if (sz != sizeof(T))
                  throw std::runtime_error
                   ("CachedObj: wrong size object in operator new");
              if (!freeStore) {
                  // the list is empty: grab a new chunk of memory
                  // allocate allocates chunk number of objects of type T
                  T * array = alloc_mem.allocate(chunk);

                  // now set the next pointers in each object in the allocated memory
                  for (size_t i = 0; i != chunk; ++i)
                        add_to_freelist(&array[i]);
              }
              T *p = freeStore;
              freeStore = freeStore->CachedObj<T>::next;
              return p;   // constructor of T will construct the T part of the object
         }
template <class T>
     void CachedObj<T>::operator delete(void *p, size_t)
     {
        if (p != 0)
            // put the "deleted" object back at head of freelist
            add_to_freelist(static_cast<T*>(p));
     }
template <class T>
     void CachedObj<T>::add_to_freelist(T *p)
     {
        p->CachedObj<T>::next = freeStore;
        freeStore = p;
     }
template <class T> allocator< T > CachedObj< T >::alloc_mem;
template <class T> T *CachedObj< T >::freeStore = 0;
template <class T> const size_t CachedObj< T >::chunk = 24;
QueueItem<Type> *pt = new QueueItem<Type>(val); //使用QueueItem<T>::operator new 函数从自由列表分配一个对象。 //再为类型 T 使用元素类型的复制构造函数,在该内存中构造一个对象。

CachedObj 只能用于不包含在继承层次中类型。与成员 new 和 delete 操作不同,CachedObj 类没有办法根据对象的实际类型分配不同大小的对象:它的自由列表保存单一大小的对象。因此,它只能用于不作基类使用的类,如 QueueItem类。

 

18.2. 运行时类型识别RTTI  (Run-time Type Identification):  程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。

  • 通过下面两个操作符提供 RTTI: 对于带虚函数的类,在运行时执行 RTTI 操作符,但对于其他类型,在编译时计算 RTTI 操作符。

         1) typeid 操作符,返回指针或引用所指对象的实际类型。

         2) dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

  

  • dynamic_cast 操作符: 可将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。

         与其他强制类型转换不同,dynamic_cast 涉及运行时类型检查。如果绑定到引用或指针的对象不是目标类型的对象,则 dynamic_cast 失败。如果转换到指针类型的 dynamic_cast 失败,则 dynamic_cast 的结果是 0 值;如果转换到引用类型的 dynamic_cast 失败,则抛出一个 bad_cast 类型的异常。

//假定 Base 是至少带一个虚函数的类,Derived 类派生于 Base 类。basePtr是指向 Base的指针
    if (Derived *derivedPtr = dynamic_cast<Derived*>(basePtr))  //非0指针转换成功
     {
         // use the Derived object to which derivedPtr points
     } else { // BasePtr points at a Base object
         // use the Base object to which basePtr points
     }

因为不存在空引用,所以不可能对引用使用用于指针强制类型转换的检查策略,相反,当转换失败的时候,它抛出一个 std::bad_cast 异常,该异常在库头文件typeinfo 中定义。

void f(const Base &b){
        try {
            const Derived &d = dynamic_cast<const Derived&>(b);
        // use the Derived object to which b referred
        } catch (std::bad_cast) {
            // handle the fact that the cast failed
        }
     }
  • dynamic_cast失败条件:如果运行时实际绑定到引用或指针的对象不是目标类型的对象(或其派生类的对象),则dynamic_cast失败。
  • typeid 操作符:如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型
typeid(e)     //e 是任意表达式或者是类型名
                //结果为std::type_info&    在头文件typeinfo中
  • typeid 最常见的用途是比较两个表达式的类型,或者将表达式的类型与特定类型相比较
         Base *bp;
         Derived *dp;
         // compare type at run time of two objects
         if (typeid(*bp) == typeid(*dp)) {
             // bp and dp point to objects of the same type
         }
         // test whether run time type is a specific type
         if (typeid(*bp) == typeid(Derived)) {
             // bp actually points to a Derived
         }
    
  • 如果指针 p 的值是 0,那么,如果 p 的类型是带虚函数的类型,则typeid(*p) 抛出一个 bad_typeid 异常;如果 p 的类型没有定义任何虚函数,则结果与 p 的值是不相关的。
  • RTTI 的使用
//比较基类和派生,3个类型就要9个操作符重载,4个类型要16个,太多。。。
     bool operator==(const Base&, const Base&)
     bool operator==(const Derived&, const Derived&)
     bool operator==(const Derived&, const Base&);
     bool operator==(const Base&, const Derived&);

//考虑用虚函数解决时,无法访问派生类特有成员

//使用RTTI dynamic_cast来访问运行时动态绑定的派生类私有成员
class Base {
         friend bool operator==(const Base&, const Base&);
     public:
         // interface members for Base
     protected:
         virtual bool equal(const Base&) const;
         // data and other implementation members of Base
     };
     class Derived: public Base {
         friend bool operator==(const Base&, const Base&);
     public:
         // other interface members for Derived
     private:
         bool equal(const Base&) const;
         // data and other implementation members of Derived
     };
 bool operator==(const Base &lhs, const Base &rhs)
     {
        // returns false if typeids are different otherwise
        // returns lhs.equal(rhs)
        return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
     }
bool Derived::equal(const Base &rhs) const
     {
        if (const Derived *dp
                   = dynamic_cast<const Derived*>(&rhs)) {
           // do work to compare two Derived objects and return result
        } else
           return false;
     }
bool Base::equal(const Base &rhs) const
     {
          // do whatever is required to compare to Base objects
     }
  • type_info 类:确切定义随编译器而变化,但是,标准保证所有的实现将至少提供以下操作

t1 == t2   如果两个对象 t1 和 t2 类型相同,就返回 true;否则,返回 false

t1 != t2   如果两个对象 t1 和 t2 类型不同,就返回 true;否则,返回 false

t.name()   返回 C 风格字符串,这是类型名字的可显示版本。类型名字用系统相关的方法产生

t1.before(t2) 返回指出 t1 是否出现在 t2 之前的 bool 值。before 强制的次序与编译器有关

  • 默认构造函数和复制构造函数以及赋值操作符都定义为 private,所以不能定义或复制 type_info 类型的对象。程序中创建 type_info 对象的唯一方法是使用 typeid 操作符。

 

18.3. 类成员的指针:只应用于类的非 static 成员

class Screen {
     public:
         typedef std::string::size_type index;
         char get() const;
         char get(index ht, index wd) const;
     private:
         std::string contents;
         index cursor;
         index height, width;
     };

//定义数据成员的指针
 Screen::index Screen::*pindex = &Screen::width;

//定义成员函数的指针:所属类的类型,返回类型,函数形参的类型和数目,包括成员是否为 const
char (Screen::*pmf2)(Screen::index, Screen::index) const = &Screen::get;

//为成员指针使用类型别名
 typedef
     char (Screen::*Action)(Screen::index, Screen::index) const;
 Action get = &Screen::get;

//使用类成员指针
//成员指针解引用操作符(.*)从对象或引用获取成员。
//成员指针箭头操作符(->*)通过对象的指针获取成员。

 // pmf points to the Screen get member that takes no arguments
     char (Screen::*pmf)() const = &Screen::get;
     Screen myScreen;
     char c1 = myScreen.get();      // call get on myScreen
     char c2 = (myScreen.*pmf)();   // equivalent call to get
     Screen *pScreen = &myScreen;
     c1 = pScreen->get();     // call get on object to which pScreen points
     c2 = (pScreen->*pmf)();  // equivalent call to get
     char (Screen::*pmf2)(Screen::index, Screen::index) const = &Screen::get;
     char c3 = myScreen.get(0,0);     // call two-parameter version of get
     char c4 = (myScreen.*pmf2)(0,0); // equivalent call to get

//使用数据成员的指针
Screen::index Screen::*pindex = &Screen::width;
     Screen myScreen;
     Screen::index ind1 = myScreen.width;      // directly
     Screen::index ind2 = myScreen.*pindex;    // dereference to get width
     Screen *pScreen;
     ind1 = pScreen->width;        // directly
     ind2 = pScreen->*pindex;      // dereference pindex to get width

//成员指针函数表

class Screen {
     public:
         Screen& home();         // cursor movement functions
         Screen& forward();
         Screen& back();
         Screen& up();
         Screen& down();
         
         typedef Screen& (Screen::*Action)();
         static Action Menu[];        // function table

         enum Directions { HOME, FORWARD, BACK, UP, DOWN };
         Screen& move(Directions);
     };
Screen::Action Screen::Menu[] = { &Screen::home,
                                       &Screen::forward,
                                       &Screen::back,
                                       &Screen::up,
                                       &Screen::down,
                                     };

Screen& Screen::move(Directions cm) {
          // fetch the element in Menu indexed by cm
          // run that member on behalf of this object
          (this->*Menu[cm])();
          return *this;
     }

     Screen myScreen;
     myScreen.move(Screen::HOME);    // invokes myScreen.home
     myScreen.move(Screen::DOWN);    // invokes myScreen.down

 

18.4. 嵌套类 Nested Classes:名字不是全局可见的

template <class Type> class Queue {
         // interface functions to Queue are unchanged
     private:
         //只有 Queue 或 Queue 的友元可以访问 QueueItem 类型,
         //所以可以用struct定义QueueItem使成员为 public 成员
         struct QueueItem {
             QueueItem(const Type &);
             Type item;            // value stored in this element
             QueueItem *next;      // pointer to next element in the Queue
         };
         QueueItem *head;      // pointer to first element in Queue
         QueueItem *tail;      // pointer to last element in Queue
     };//在类外部定义的嵌套类成员,构造函数template <class Type> Queue<Type>::QueueItem::QueueItem(const Type &t): item(t), next(0) { } 

因为 Queue 类是一个模板,它的成员也隐含地是模板。具体而言,嵌套类QueueItem 隐含地是一个类模板。

Queue 类的每次实例化用对应于 Type 的适当模板实参产生自己的 QueueItem类。QueueItem 类模板的实例化与外围 Queue 类模板的实例化之间的映射是一对一的。

  • 在外围类外部定义嵌套类
template <class Type> class Queue { 
     private:
         struct QueueItem; // 嵌套类的前向声明,在实际定义前,该类为不完全类型,不能创建对象,
                       //只能定义指针或引用,或声明(而非定义)用该类型作为形参或返回类型的函数
         QueueItem *head;  // pointer to first element in Queue
         QueueItem *tail;  // pointer to last element in Queue
     };
     template <class Type>
     struct Queue<Type>::QueueItem {
         QueueItem(const Type &t): item(t), next(0) { }
         Type item;        // value stored in this element
         QueueItem *next; // pointer to next element in the Queue
     };
  • 嵌套类中的非静态函数具有隐含的 this 指针,指向嵌套类型的对象。同样,外围类中的非静态成员函数也具有 this 指针,它指向外围类型的对象。

          嵌套类可以直接引用外围类的静态成员、类型名和枚举成员。

  • 实例化外围类模板的时候,不会自动实例化类模板的嵌套类。像任何成员函数一样,只有当在需要完整类类型的情况下使用嵌套类本身的时候,才会实例化嵌套类。
  • 处理类成员声明的时候,所用的任意名字必须在使用之前出现。当处理定义的时候,整个嵌套类和外围类均在作用域中。
class Outer {
     public:
         struct Inner {
             // ok: reference to incomplete class
             void process(const Outer&);
             Inner2 val; // error: Outer::Inner2 not in scope
         };
         class Inner2 {
         public:
             // ok: Inner2::val used in definition
             Inner2(int i = 0): val(i) { }
             // ok: definition of process compiled after enclosing class is complete
             void process(const Outer &out) { out.handle(); }
         private:
             int val;
         };
         void handle() const; // member of class Outer
     };

 

18.5. 联合:节省空间的类

  • 一个 union 对象可以有多个数据成员,但在任何时刻,只有一个成员可以有值。当将一个值赋给 union 对象的一个成员的时候,其他所有都变为未定义的。

        每个 union 对象的大小在编译时固定的:它至少与 union 的最大数据成员一样大。

  • union 可以指定保护标记使成员成为公用的、私有的或受保护的。默认情况下,union 表现得像 struct

    union 也可以定义成员函数,包括构造函数和析构函数。但是,union 不能作为基类使用,所以成员函数不能为虚数。

    union 不能具有静态数据成员或引用成员,而且,union 不能具有定义了构造函数、析构函数或赋值操作符的类类型的成员

    union illegal_members {
             Screen s;      // error: has constructor
             static int is; // error: static member
             int &rfi;      // error: reference member
             Screen *ps;    // ok: ordinary built-in pointer type
         };
    
    TokenValue first_token = {'a'};  // 显式初始化只能为第一个成员提供初始化式。
    TokenValue last_token;           // uninitialized TokenValue object
    TokenValue *pt = new TokenValue; // pointer to a TokenValue object
    last_token.cval = 'z';
    pt->ival = 42;
    

给 union 对象的某个数据成员一个值使得其他数据成员变为未定义的。通过错误的数据成员检索保存在 union 对象中的值,可能会导致程序崩溃或者其他不正确的程序行为。

避免通过错误成员访问 union 值的最佳办法是,定义一个单独的对象跟踪 union中存储了什么值。这个附加对象称为 union 的判别式discriminant

  • 匿名联合:  不能有私有成员或受保护成员,也不能定义成员函数
class Token {
     public:
         enum TokenKind {INT, CHAR, DBL};
         TokenKind tok;   //判别式,跟踪 union 
         union {                 // anonymous union
             char   cval;
             int    ival;
             double dval;
         };
     };
Token token;
     switch (token.tok) {
         case Token::INT: 
             token.ival = 42; break; //匿名 union 的成员的名字出现在外围作用域中
     	 case Token::CHAR:
             token.cval = 'a'; break;
         case Token::DBL:
             token.dval = 3.14; break;
     }

18.6. 局部类(Local Classes): 在函数体内部定义的类

  • 局部类的所有成员(包括函数)必须完全定义在类定义体内部,因此,局部类远不如嵌套类有用。

          不允许局部类声明 static 数据成员,没有办法定义它们。

  • 局部类只能访问在外围作用域中定义的类型名、static 变量和枚举成员,不能使用定义该类的函数中的变量
     int a, val;
     void foo(int val)
     {
        static int si;
        enum Loc { a = 1024, b };
        // Bar is local to foo
        class Bar {
        public:
            Loc locVal; // ok: uses local type name
            int barVal;
            void fooBar(Loc l = a)         // ok: default argument is Loc::a
            {
               barVal = val;      // error: val is local to foo
               barVal = ::val;    // ok: uses global object
               barVal = si;       // ok: uses static local object
               locVal = b;        // ok: uses enumerator
            }
        };
        // ...
     }

可以将一个类嵌套在局部类内部。这种情况下,嵌套类定义可以出现在局部类定义体之外,但是,嵌套类必须在定义局部类的同一作用域中定义。照常,嵌套类的名字必须用外围类的名字进行限定,并且嵌套类的声明必须出现在局部类的定义中

void foo()
     {
        class Bar {
        public:
            // ...
            class Nested;    // declares class Nested
        };
        //  definition of Nested
        class Bar::Nested {
            // ...
        };
     }
//嵌套在局部类中的类本身是一个带有所有附加限制的局部类。
//嵌套类的所有成员必须在嵌套类本身定义体内部定义

 

18.7. 固有的不可移植的特征 (Inherently Nonportable Features)

算术类型的大小随机器不同而变化,位域和 volatile 限定符,链接指示(它使得可以链接到用其他语言编写的程序)

  • 18.7.1. 位域 (Bit-fields)

          保存特定的位数。当程序需要将二进制数据传递给另一程序或硬件设备的时候,通常使用位域。

         位域在内存中的布局是机器相关的。

         位域必须是整型数据类型,可以是 signed 或 unsigned。通过在成员名后面接一个冒号以及指定位数的常量表达式,指出成员是一个位域

         地址操作符(&)不能应用于位域,所以不可能有引用类位域的指针,位域也不能是类的静态成员.

typedef unsigned int Bit;

     class File {
         Bit mode: 2;
         Bit modified: 1;
         Bit prot_owner: 3;
         Bit prot_group: 3;
         Bit prot_world: 3;
         
     public:                         //通常使用内置按位操作符操纵超过一位的位域
        enum { READ = 01, WRITE = 02 }; // File modes
	inline int isRead() { return mode & READ; }
     	inline int isWrite() { return mode & WRITE; }
	void setRead() { mode |= READ;} // set the READ bit

     };

(如果可能)将类定义体中按相邻次序定义的位域压缩在同一整数的相邻位,从而提供存储压缩。例如,在前面的声明中,5 个位域将存储在一个首先与位域 mode关联的 unsigned int 中。位是否压缩到整数以及如何压缩与机器有关.

通常最好将位域设为 unsigned 类型。存储在 signed 类型中的位域的行为由实现定义。

 

18.7.2. volatile 限定符

volatile 的确切含义与机器相关,只能通过阅读编译器文档来理解。使用volatile 的程序在移到新的机器或编译器时通常必须改变。

当可以用编译器的控制或检测之外的方式改变对象值的时候,应该将对象声明为volatile。关键字 volatile 是给编译器的指示,指出对这样的对象不应该执行优化。

类也可以将成员函数定义为 volatilevolatile 对象只能调用 volatile 成员函数。

可以声明 volatile 指针、指向 volatile 对象的指针,以及指向 volatile 对象的 volatile 指针:

像用 const 一样,只能将 volatile 对象的地址赋给指向 volatile 的指针,或者将指向 volatile 类型的指针复制给指向 volatile 的指针。只有当引用为volatile 时,我们才可以使用 volatile 对象对引用进行初始化。

合成的复制控制不适用于 volatile 对象,因为不能将 volatile 对象传递给普通引用或 const 引用。

如果类希望允许复制 volatile 对象,或者,类希望允许从 volatile 操作数或对volatile 操作数进行赋值,它必须定义自己的复制构造函数和/或赋值操作符版本

 class Foo {
     public:
         Foo(const volatile Foo&);    // copy from a volatile object
         // assign from a volatile object to a non volatile objet
         Foo& operator=(volatile const Foo&);
         // assign from a volatile object to a volatile object
         Foo& operator=(volatile const Foo&) volatile;
         // remainder of class Foo
     };
//通过将复制控制成员的形参定义为 const volatile 引用,我们可以从任何各类的 Foo 对象进行复制或赋值:普通 Foo 对象、const Foo 对象、volatile Foo对象或 const volatile Foo 对象。

 

18.7.3. 链接指示 extern "C"  指出任意非 C++ 函数所用的语言。

  • 链接指示有两种形式:单个的或复合的。链接指示不能出现在类定义或函数定义的内部,它必须出现在函数的第一次声明上。
// illustrative linkage directives that might appear in the C++ header <cstring>
     // single statement linkage directive
     extern "C" size_t strlen(const char *);
     // compound statement linkage directive
     extern "C" {
         int strcmp(const char*, const char*);
         char *strcat(char*, const char*);
     }
// compound statement linkage directive
     extern "C" {
     #include <string.h>     // C functions that manipulate C-style strings
     }

有时需要在 C 和 C++ 中编译同一源文件。当编译 C++ 时,自动定义预处理器名字 __cplusplus(两个下划线),所以,可以根据是否正在编译 C++ 有条件地包含代码。

#ifdef __cplusplus
     // ok: we're compiling C++
     extern "C"
     #endif
     int strcmp(const char*, const char*);
C 语言不支持函数重载,在一组重载函数中只能为一个 C 函数指定链接指示
// error: two extern "C" functions in set of overloaded functions
     extern "C" void print(const char*);
     extern "C" void print(int);
  • extern "C" 函数和指针

      C 函数的指针与 C++ 函数的指针具有不同的类型,不能将 C 函数的指针初始化或赋值为 C++ 函数的指针(反之亦然)。

     void (*pf1)(int);            // points to a C++ function
     extern "C" void (*pf2)(int); // points to a C function
     pf1 = pf2; // error: pf1 and pf2 have different types

因为链接指示应用于一个声明中的所有函数,所以必须使用类型别名,以便将 C 函数的指针传递给 C++ 函数

// FC is a pointer to C function
     extern "C" typedef void FC(int);
     // f2 is a C++ function with a parameter that is a pointer to a C function
     void f2(FC *);

没有更多推荐了,返回首页