1.为啥要重载操作符:

通过重载操作符,程序员可以针对“类”类型的操作数定义不同的操作符版本。良好的操作符定义可以使class类型的使用想内置类型一样直观简洁,使用重定义的操作符而不是命名函数使得程序可以用表达式代替函数调用,使程序编写和阅读更容易~

2.哪些不能重载

::     .*      .     ?:    这些不能重载

3.需要注意的地方:

重载必须有一个class类型的操作数,短路求值失去,

重载操作符和内置操作符结合型相同,优先级操作数个数均相同

不要重载一些含有内置定义的操作符 & , && || 这些

·赋值(=)下标(【】)调用 (())和成员访问操作符必须定义为成员

·对称的操作符一般定义为普通非成员函数

++ -- 一般设置为成员函数 ,因为可能改变对象状态

4.定义输入输出操作符

io操作符只能重载为非成员函数,否则做操作符只能是对象成员 用法变成了 object<<cin  不符合我们的习惯,经常把他们设置成为友元,因为可能触及私有变量。

输入必须加入文件结束和输入错误的错误处理

istream&
operator>>(istream& in, Sales_item& s)
{
     double price;
     in >> s.isbn >> s.units_sold >> price;
     // check that the inputs succeeded
     if (in)
        s.revenue = s.units_sold * price;
     else
        s = Sales_item(); // input failed: reset object to default state
     return in;
}

如果输入失败,调用默认构造函数恢复对象状态。

5.算术运算符和关系运算符

Sales_item& operator -=(const Sales_item& item){
    units_sold-=item.units_sold;
    revenue-=item.revenue;
    return *this;
}
Sales_item operator - (const Sales_item& lhs,const Sales_item& rhs){
    Sales_item res(lhs);
    res+=rhs;
    return res;
}

 

一般算术运算符设置为非成员函数,与内置运算符对应,选择返回value 而不是引用。赋值运算符重载为成员函数,并用它来实现算术运算符,这样算术运算符不用friend

相等运算符和不等运算符一般成对出现,且用一个实现另一个

关系运算符 == != > < 一般重载成非成员函数

6.赋值操作符

必须为成员函数 (=号)

=和+=  -= 一般都需要返回左操作数的引用

Sales_item& operator = (string is){
    isbn = is;
    return *this;
}

6.下标操作符

必须为类成员函数   返回引用使其可以在赋值操作的任意一边

一般定义一种const 返回常量引用 一种not const 引用

     class Foo {
     public:
         int &operator[] (const size_t);
         const int &operator[] (const size_t) const;
         // other interface members
     private:
         vector<int> data;
         // other member data and private utility functions
      };

int& Foo::operator[] (const size_t index)
     {
         return data[index];  // no range checking on index
     }
     const int& Foo::operator[] (const size_t index) const
     {
         return data[index];  // no range checking on index
     }

pari<string,string>& operator[] (const  vector< pair<string,string>* > ::size_type index){
    return *wait_list.at(index);//使用at判断是否越界
}

6.成员访问操作符

-> 一般要求重载为成员运算符,*没有要求 ,但成员比较常见~~~

例子:auto-ptr~~~~

ScreenPtr 的用户将会传递一个指针,该指针指向动态分配的 ScreenScreenPtr 类将拥有该指针,并安排在指向基础对象的最后一个 ScreenPtr 消失时删除基础对象。另外,不用为 ScreenPtr 类定义默认构造函数。因此,我们知道一个 ScreenPtr 对象将总是指向一个 Screen 对象,不会有未绑定的 ScreenPtr,这一点与内置指针不同。应用程序可以使用 ScreenPtr 对象而无须首先测试它是否指向一个 Screen 对象。

     // private class for use by ScreenPtr only 私有类,
     class ScrPtr {
         friend class ScreenPtr;
         Screen *sp;
         size_t use;
         ScrPtr(Screen *p): sp(p), use(1) { }
         ~ScrPtr() { delete sp; }
     };
        /*
      * smart pointer: Users pass to a pointer to a dynamically allocated Screen, which
      *                   is automatically destroyed when the last ScreenPtr goes away
      */
     class ScreenPtr {
     public:
         //  no default constructor: ScreenPtrs must be bound to an object
         ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }
         //  copy members and increment the use count
         ScreenPtr(const ScreenPtr &orig):
            ptr(orig.ptr) { ++ptr->use; }
         ScreenPtr& operator=(const ScreenPtr&);
         //  if use count goes to zero, delete the ScrPtr object
         ~ScreenPtr() { if (--ptr->use == 0) delete ptr; }
     private:
         ScrPtr *ptr;    // points to use-counted ScrPtr class
     };

指针支持的基本操作有解引用操作和箭头操作。我们的类可以这样定义这些操作:

     class ScreenPtr {
     public:
         // constructor and copy control members as before
         Screen &operator*() { return *ptr->sp; }
         Screen *operator->() { return ptr->sp; }
         const Screen &operator*() const { return *ptr->sp; }
         const Screen *operator->() const { return ptr->sp; }
     private:
         ScrPtr *ptr; // points to use-counted ScrPtr class
     };

解引用操作符是个一元操作符。在这个类中,解引用操作符定义为成员,因此没有显式形参,该操作符返回对 ScreenPtr 所指向的 Screen 的引用。

箭头操作符不接受显式形参。point->action();   等价于  (point->action)();

可以这样使用 ScreenPtr 对象访问 Screen 对象的成员:

ScreenPtr p(&myScreen);     // copies the underlying Screen
p->display(cout);

因为 p 是一个 ScreenPtr 对象,p->display 的含义与对 (p.operator->())->display 求值相同。对 p.operator->() 求值将调用 ScreenPtr 类的 operator->,它返回指向 Screen 对象的指针,该指针用于获取并运行 ScreenPtr 所指对象的 display 成员。

重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。

6.自增自减操作符

一般重载为成员函数,为了与内置类型一致,前置操作符返回运算结果引用,后置操作符返回运算前的值,value not ref ,为了区分,后置操作符提供了一个实参0;

// prefix: return reference to incremented/decremented object
    CheckedPtr& CheckedPtr::operator++()
    {
        if (curr == end)
            throw out_of_range
                  ("increment past the end of CheckedPtr");
        ++curr;                // advance current state
        return *this;
    }

CheckedPtr CheckedPtr::operator++(int)
     {

         // no check needed here, the call to prefix increment will do the check
         CheckedPtr ret(*this);        // save current value
         ++*this;                      // advance one element, checking the increment 用前置实现它,不用判断出界了
         return ret;                   // return saved state
     }

显式调用:

CheckedPtr parr(ia, ia + size);        // iapoints to an array of ints
parr.operator++(0);                    // call postfix operator++
parr.operator++();                     // call prefix operator++

7 调用操作符和函数对象

struct absInt {
       int operator() (int val) {
           return val < 0 ? -val : val;
       }
   };

 

通过为类类型的对象提供一个实参表而使用调用操作符,所用的方式看起来像一个函数调用:

     int i = -42;
     absInt absObj;  // object that defines function call operator
     unsigned int ui = absObj(i);     // calls absInt::operator(int)

函数调用操作符必须声明为成员函数。一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别。

定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象。

函数:

     // determine whether a length of a given word is 6 or more
     bool GT6(const string &s)
     {
         return s.size() >= 6;
     }

函数对象:

// determine whether a length of a given word is longer than a stored bound
     class GT_cls {
     public:
         GT_cls(size_t val = 0): bound(val) { }
         bool operator()(const string &s)
                            { return s.size() >= bound; }
     private:
         std::string::size_type bound;
     };

for (size_t i = 0; i != 11; ++i)
     cout << count_if(words.begin(), words.end(), GT(i))
          << " words " << i
          << " characters or longer" << endl;

函数对象的便捷性】

         plus<int> intAdd;         // function object that can add two int values
     negate<int> intNegate;   //  function object that can negate an int value
     // uses intAdd::operator(int, int) to add 10 and 20
     int sum = intAdd(10, 20);          // sum = 30
     // uses intNegate::operator(int) to generate -10 as second parameter
     // to intAdd::operator(int, int)
     sum = intAdd(10, intNegate(10));    // sum = 0

函数适配器:

banding器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象

求反器是一种函数适配器,它将谓词函数对象的真值求反。标准库定义了两个求反器:not1not2 分别求反一元二元对象

8。实参匹配和转换(俺来看重载操作符的原因啊,,,)

转换操作符是一种特殊的类成员函数。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型:

转换函数采用如下通用形式:

     operator type();

转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。

虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。例如,operator int 返回一个 int 值;如果定义 operator Sales_item,它将返回一个 Sales_item 对象,诸如此类。

转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为 const 成员。

只要存在转换,编译器将在可以使用内置转换的地方自动调用它

  • In expressions:

    在表达式中:

         SmallInt si;
         double dval;
         si >= dval          // si converted to int and then convert to double
  • In conditions:

    在条件中:

         if (si)                // si converted to int and then convert to bool
  • When passing arguments to or returning values from a function:

    将实参传给函数或从函数返回值:

         int calc(int);
         SmallInt si;
         int i = calc(si);      // convert si to int and call calc
  • As operands to overloaded operators:

    作为重载操作符的操作数:

         // convert si to int then call opeator<< on the int value
         cout << si << endl;
  • In an explicit cast:

    在显式类型转换中:

         int ival;
         SmallInt si = 3.541; //
         instruct compiler to cast si to int
         ival = static_cast<int>(si) + 3;
    

    类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。(指的是不能连续两个自定义的类型转换,但是内置类型转换可以的)

  • 还有一部分是实参匹配和转换 ,没时间了 以后再看~~~~

     

     

     

     

     

     

     

     

     

     

     

    posted @ 2010-03-15 16:40 rikisand 阅读(1224) | 评论 (0)编辑 收藏

    在复习和学习新知识面前,人们总是倾向于学习新的知识,以获得更大的满足感与成就感。写博客其实更大程度是为了让自己复习,而不是将这种学习的成就感实体化。如果写了记录但从没有回头去看,去复习,去温故,当下次遇到问题时只能从头学起。与第一次的区别仅仅在于,我知道我曾经记过而已~

    所以,每周至少应该抽出一天的时间,来回顾上周记下的东西。在这一天不要贪图学习更多的新的知识,如果通过复习把上周记下的知识巩固,加深领会,已经是很大的收获了···

    posted @ 2010-03-14 12:34 rikisand 阅读(175) | 评论 (0)编辑 收藏

       首先,可重入和线程安全是两个并不等同的概念,一个函数可以是可重入的,也可以是线程安全的,可以两者均满足,可以两者皆不满组(该描述严格的说存在漏洞,参见第二条)。
        其次,从集合和逻辑的角度看,可重入是线程安全的子集,可重入是线程安全的充分非必要条件。可重入的函数一定是线程安全的,然过来则不成立。
        第三,POSIX 中对可重入和线程安全这两个概念的定义:
    Reentrant Function:
        A function whose effect, when called by two or more threads,is guaranteed to be as if the threads each executed thefunction one after another in an undefined order, even ifthe actual execution is interleaved.
                                                                                                            From IEEE Std 1003.1-2001 (POSIX 1003.1)
                                                                                                                                          -- Base Definitions, Issue 6
    Thread-Safe Function:
        A function that may be safely invoked concurrently by multiple threads.
       另外还有一个 Async-Signal-Safe的概念
    Async-Signal-Safe Function:
        A function that may be invoked, without restriction fromsignal-catching functions. No function is async-signal -safe unless explicitly described as such.
        以上三者的关系为:
    Reentrant Function 必然是Thread-Safe Function和Async-Signal-Safe Function
    可重入与线程安全的区别体现在能否在signal处理函数中被调用的问题上,可重入函数在signal处理函数中可以被安全调用,因此同时也是Async-Signal-Safe Function;而线程安全函数不保证可以在signal处理函数中被安全调用,如果通过设置信号阻塞集合等方法保证一个非可重入函数不被信号中断,那么它也是Async-Signal-Safe Function。
         值得一提的是POSIX 1003.1的System Interface缺省是Thread-Safe的,但不是Async-Signal-Safe的。Async-Signal-Safe的需要明确表示,比如fork ()和signal()。
    最后让我们来构想一个线程安全但不可重入的函数:
       假设函数func()在执行过程中需要访问某个共享资源,因此为了实现线程安全,在使用该资源前加锁,在不需要资源解锁。
       假设该函数在某次执行过程中,在已经获得资源锁之后,有异步信号发生,程序的执行流转交给对应的信号处理函数;再假设在该信号处理函数中也需要调用函数func(),那么func()在这次执行中仍会在访问共享资源前试图获得资源锁,然而我们知道前一个func()实例已然获得该锁,因此信号处理函数阻塞——另一方面,信号处理函数结束前被信号中断的线程是无法恢复执行的,当然也没有释放资源的机会,这样就出现了线程和信号处理函数之间的死锁局面。
        因此,func()尽管通过加锁的方式能保证线程安全,但是由于函数体对共享资源的访问,因此是非可重入。

    posted @ 2010-03-12 14:33 rikisand 阅读(247) | 评论 (0)编辑 收藏

    #include <iostream> 
    #include <vector>
    #include "time.h"
    using namespace std; 
    void sieve(int n){
        vector<bool> isprime(n,true);
        vector<int> prime;
        int cnt=0;
        for(int i=2;i<n;i++){
            if(isprime[i])cnt++,prime.push_back(i);
            for(int t=0;t<cnt&&i*prime[t]<n;t++){
                isprime[i*prime[t]]=false;
                if(i%prime[t]==0)break;
            }
        }
        /*for(int i=0;i<cnt;i++)
            cout<<prime[i]<<" ";*/
    }
    void oldseive(int n){
        vector<bool> isprime(n,true);
        vector<int> prime;
        for(int i=2;i<n;i++){
            if(isprime[i]){
                prime.push_back(i);
                for(int j=i*2;j<n;j+=i)
                    isprime[j]=false;
            }
        }
        /*for(int i=0;i<prime.size();i++)
            cout<<prime[i]<<" ";*/
    }
    int main(){
        clock_t start,end;
        start = clock();
         sieve(2000000);
         //oldseive(2000000);
        end  = clock();
        double time = double(end-start)/CLOCKS_PER_SEC;
        cout<<endl<< time<<endl;
    } 

    线性筛法sieve 1.546s oldsieve 2.875s 快了将近一倍

    old sieve 缺陷:合数可能被多次筛掉,例如 30被2,3,5筛掉了3次 然后 线性筛法限定了 任何一个合数只被它的最小质因数筛掉一次,怎么做到这一点~~

    if(i%prime[t]==0) break; 如果此时筛掉的合数从小到大找到第一个可以整除的质数,那么显然他找到了它的最小质因数,此时我们停止搜索质数表,因为后面的质数比当前的prime[t]要大,如果我们用prime[t+n]*i 筛掉了一个合数,这个合数必然可以表述成为 prime[t]*someK  *prime[t+n] 也就是说这个合数的最小质因数也是prime[t],他应该被 prime[t]筛掉-->当程序运行到 someK*prime[t+n] 的时候~~~~

    over--------------------------------------------------------------------

    posted @ 2010-03-12 14:04 rikisand 阅读(1541) | 评论 (0)编辑 收藏

    构造,析构,拷贝语义学 semantics of construction destruction and copy

    纯虚拟函数的存在(Presence of a Pure Virtual Function)

    纯虚拟函数是可以定义并调用的,只不过只能被静态调用,不能经由虚拟机制。(经过试验vs2008是不可以的)

    而pure virtual destructor :class 设计者一定要定义它。因为每一个derived class的destructor会被编译器加以扩展,以静态方式调用其每一个virtual base class 以及上层base class 的destructor ,因此缺乏任何一个base class destructor 的定义就会导致连接失败

    基类的析构函数应该设置为虚拟的,不一定是纯虚的 ,除非在希望将一个类变成抽象类(不能实例化的类),而这个类又没有合适的函数可以被纯虚化的时候,可以使用纯虚的析构函数来达到目的。

    class A{ public: char* pChar;A(){pChar=new char[20];};~A(){delete []pChar;}};

    class B:public A{ public: char* pChar;B(){pChar=new char[20];}~B(){delete []pChar;}};

    int main(){ A* a = new B; delete a;}

    如上,如果A的析构函数没有设置为虚拟的,那么delete a 的时候只有调用了A的析构函数,真正的B并没有调用。而如果虚拟的话,那么a调用析构函数会调用B的析构函数,而B的析构函数会调用A的析构函数。

    虚拟函数的取舍:如果一个函数被设计为虚拟函数,但是他函数定义并不与类型相关,也就是说继承类改写他的几率很低,那么这是个糟糕的事情。如果不是虚拟而且能内联,这是个很大的损失

    ---------

    没有继承下的对象构造

    Plain OI’s data 形式

    例如 struct {float x,y,z;}

    理论上编译器会生成没有用的trival ctor dtor copytor copy operator ,然而事实上什么也没有发生。

    全局对象,c中会被放在bss段,而c++中的全局对象会被视为完全定义,被当做初始化过的数据来对待。

    抽象数据类型:

    class Point {public: Point (float x=0):_x(x){}; private: _x;}

    经过封装的Point class 大小没有改变 由于默认的member wise 赋值语义已经足够,我们无需提供copy constructor 和copy operator。对于global 实体,Point::Point(0) 会自动调用 (程序开始时候startup())

    如果要对class所有成员都设定常量初值,那么给予一个explicit initial list 会比较高效。

    例如 void mumble(){

    Point A1={1};

    Point A2; A2._x=1;

    }

    A2的初始化会比A1慢,因为,当activation record(活动记录 是不是栈中的pc 指针),上述的list中常量就可以放进A1的内存中了

    explicit initialzation list (A1的那种)缺点:public 才可以;只能指定常量;

    Point *heap = new Point;

    翻译为:

    Point *heap = __new(sizeof(Point));

    if(heap)heap->Point::Point();

    `````

    为继承准备:

    class Point{
    public:
        Point(float x=0.,float y=0.):_x(x),_y(y){}
        virtual float z();
    protected:
        float _x,_y;
    };

    这里并没有定义copy constructor ,copy operator,我们所有member 都是数值,因此默认语义下情况良好。virtual destructor 也没有引入啊,同样道理,行为良好即可

    virtual func 的引入给我们的 Point class 带来了膨胀作用:

    定义的构造函数被附加了一些代码,用来初始化vptr 这些代码附加在任何base 初始化之后,任何程序员代码之前。例如:

    Point* Point::Point(Point* this,float x,float y):_x(x),_y(y){

      this->_vptr_Point=_vtbl_Point; this->_x=x;this->_y=y; return this;

    }

    合成一个copy constructor 和一个 copy operator 而且他们也不再是trival

    因为bitwise的赋值可能会带给vptr 错误

    ···············

    继承体系下的对象构造:

    如果定义 T object 会发生什么

    1.member initialization list 中的data number 初始化操作会放到ctor 中并以声明顺序排列

    2.如果一个member没有出现在 list 中但他有default constructor ,他必须被调用

    3.在那之前,如果class object有vptr 那么他必须设定初值指向正确vtable

    4.在那之前,任何上一层base class ctor 必须被调用,以base class 声明顺序,

    如果base class 列于 初始化列表中,参数被传递

    如果base class 不再列表,有默认ctor 调用

    如果base class 是多重继承下 第二或者后继的base class this 指针调整

    5.在那之前virtual base class ctor 必须被调用 从左至右,由深到浅

    如果base class 列于 初始化列表中,参数被传递如果base class 不再列表,有默认ctor 调用

    base class subobject 的offset 必须在执行期可以被存取

    ··························

    虚拟继承

    虚拟继承比较具有特殊性,因为他具有共享性。在这样一个继承体系中

    image 

    Point 3d 和Vertex 虚拟继承自Point  ,此时如果按照普通的ctor 规则。Vertex 的ctor 必须调用Point 的ctor ,然而 当  Point3d 和 Vertex 同为Vertex3d的subobject时,他们的调用一定不能发生(this指针的调整问题) ,而只能有 Vertex3d才可以调用,同样,PVertex构造时候只能由他自己调用,也就是说只有底层的class 完成构建共享的subobject构造。我们可以用一个most _derived参数传给各个构造函数以指示其是否调用共享部分的ctor。

    事实上我们发现,只有当object 是完整object时才会调用共享部分的ctor ,而部分subobject不会调用,从而得到新的策略,提供两种构造函数一种供完整的object 一种供subobject调用

     

    ~~~~vptr 初始化语义学~~~

    c++语言告诉我们:在Point3d ctor 调用size函数,必须决议为 point3d  的size 而不是 pvertex的size,可以理解为在ctor和dtor 中虚函数决议为自己的函数,他不虚啦~~~~~(实际上是因为他没有变身完全)

    ctor 调用顺序是从根源到末端,从内而外。当base class ctor 执行时候 derived实体还没有构造出来,在pvertex 构造完整之前,pvertex并不是一个完整的对象,因此只有Point3d subobject构造完毕,这意味着每一个pvertex base class ctor 调用时候,编译器必须保证有适当的 size 函数实体来调用 how,so 决议为自己的size

    另一种方法是控制vptr,

    在base class ctor 调用之后,但是在程序员提供代码或者initial list 之前,我们设定vptr ,也就是说我们保证vptr指向刚刚构造完毕的base clas subobject 对应的vtable ,保证了它能够调用正确的virtual func;

    所以对象构造是这样的过程:

    一个 PVertex对象会先形成一个 Point对象- >Point3d->Vertex->Vertex3d->PVertex

    ctor执行算法:

    1.derived class ctor 中所有 virtual base class 和上一级base class 调用

    2. 上述完成vptr 初始化,指向相应的vtable

    3.如果有member initial list 的haunted,将在ctor 展开,这些必须在vptr 设定后进行,防止有virtual func 调用

    4.最后执行程序员写的代码

    vptr 需要被设定的两种情况:

    1.对象完整定义后

    2.当一个subobject ctor 调用了一个虚拟函数时候(上面说的很明白喽)

    当声明一个PVertex对象时候,由于我们对base class ctor 的定义,其vptr 不需要每一个base class ctor 中设定,因此我们可以把ctor 分解成两部分,一种完整的object  实体,一种subobject实体,subobject实体中vptr 设定可以是省略

    如果在class ctor initiallist 调用该class 虚拟函数安全么? 理论上安全,但是如果依赖未初始化member 不安全

    ``````````````````

    对象复制语义学 object copy semantics

    我们有三种选择:

    1 什么都不做,按默认行为实施

    2 提供一个explicit copy assignment operator

    3 明确拒绝把一个class object 指定给一个class object (声明一个private 的copy assignment operator)

    下列情况下,class 不表现出bitwise 的复制语义,也就是说默认的copy operator 是不够的:(和之前介绍的一样)

    1.class 有一个member object 而他有copy operator

    2.class的base class 有一个copy operator

    3.当class 声明virtual func时 (由于要设定vptr when derived to base)

    4.class 继承自 virtual base class

    同样在这里也面临ctor 中的问题(虚拟继承时),最底层的class 必须调用共享的base class的copy operator,一种策略是,并不压制上面的class 调用自己的copy operator ,也就是说允许了 共享部分的多重复制。另外一种方法是不要允许virtual base class 的copy 操作,甚至不要再任何virtual base class 中声明数据~

    ··········析构语义学semantics of Destruction

    如果class 没有定义dtor 那么只有class 内带的member object 或者自己的base class 有dtor 时候,编译器才会合成一个来,否则dtor 被视为不需要了~~~(有virtual func 也不一定需要的)

    dtor 调用顺序是这样的:

    1 dtor  函数本身执行,

    2 如果class 拥有member class object 而后者拥有dtor 那么他们会以相反的顺序被调用

    3 如果object 带有vptr ,现在被重新设定,指向适当的base class vtble

    4 如果任意直接的nonvirtual base class 有dtor ,他们会以声明的相反顺序调用

    5 如果任意virtual base class 有destructor ,而当前讨论的这个class 是最低端的most –derived class 那么他们会以原来的构造顺序相反顺序调用

     

    一个object 的生命周期结束于dtor 开始执行时,由于每一个base class dtor 轮番调用,所以一个derived object 实际上变成了一个个完整的objec ;一如 PVertex->Vertex3d->Vertex->Point3d->Point

    对象的蜕变会因为vptr 重新设定受到影响(dtor中,程序员代码之前),在程序中施行dtor的真正语义会在下一章具体表述~~

    #include <time.h>
    #include <iostream> 
    using namespace std;  
    class memberclass{
    public: 
         memberclass(){cout<<"memberclass default ctor"<<endl;}
        memberclass(int x){cout<<"memberclass ctor with parameter"<<endl;}
        ~memberclass(){cout<<"memberclass dtor"<<endl;}
    };
    class Point{public: Point(){cout<<"point ctor"<<endl;test();}
    ~Point(){cout<<"point dtor"<<endl;test();}
    virtual void test(){cout<<"point test"<<endl;}
    };
    class Point3d:virtual public Point{public: Point3d(){cout<<"Point3d ctor"<<endl;test();}
    ~Point3d(){cout<<"Point3d dtor"<<endl;test();}
    virtual void test(){cout<<"Point3d test"<<endl;}};
    class vertex:virtual public Point{public: vertex(){cout<<"vertex ctor"<<endl;test();}
    ~vertex(){cout<<"vertex dtor"<<endl;test();}
    virtual void test(){cout<<"vertex test"<<endl;}
    };
    class vertex3d:public Point3d ,public vertex{public: 
    memberclass inside;
    vertex3d(){cout<<"vertex3d ctor"<<endl;test();}
    ~vertex3d(){cout<<"vertex3d dtor"<<endl;test();}
    virtual void test(){cout<<"vertex3d test"<<endl;}
    };
    class pvertex:public vertex3d{public: 
    memberclass inside;
    pvertex():inside(3){cout<<"pvertex ctor"<<endl;test();}
    ~pvertex(){cout<<"pvertex dtor"<<endl;test();}
    virtual void test(){cout<<"pvertex test"<<endl;}
    };
    int main(){ 
        pvertex* p = new pvertex;
        delete p;
        return 0;
    }
    输出: guess it ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     

    point ctor
    point test
    Point3d ctor
    Point3d test
    vertex ctor
    vertex test
    memberclass default ctor
    vertex3d ctor
    vertex3d test
    memberclass ctor with parameter
    pvertex ctor
    pvertex test
    pvertex dtor
    pvertex test
    memberclass dtor
    vertex3d dtor
    vertex3d test
    memberclass dtor
    vertex dtor
    vertex test
    Point3d dtor
    Point3d test
    point dtor
    point test

     

    ---------------------------------------------------OVER--------------------------------------------------------

     
     
     
     
     
     
     
     
     
     
     
     
     
     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    posted @ 2010-03-12 11:06 rikisand 阅读(1839) | 评论 (0)编辑 收藏

    Function 语义学

    ····member 的各种调用方式~

    1.非静态成员函数·

    float Point3d::mangitude3d()const{…}

    会变成 float Point3d::magnitude(const Point3d* this){…}

    c++的准则之一:非静态成员函数至少必须同一般的nonmember function 有相同的效率

    名称的特殊处理:(name mangling)一般member名称前面都会加上class名称,形成独一无二的命名,class Bar {public :int ival;} 可能变成 ival_3Bar ;3应该是bar的长度了。

    这样可以防止继承体系中两个类定义同样名字的变量~

    如果使用extern “C” 就可以压制nonmember 的mangling 效果

    2.虚拟成员函数·

    如果normalize 是虚拟函数 他会被翻译成:

    (*ptr->vptr[1])(ptr); 第二个ptr是this指针

    类似的magnitude 会变成

    (*this->vptr[2])(this);

    而magnitude是在normalize之后调用的因此此时已经确定this指向的是Point3d 因此可以直接调用Point3d::magnitude()更有效率

    如果用一个对象调用一个虚拟函数应该把它当做正常函数来对待,因为可以确定对象类型直接调用相应的函数即可,在这种情况下,虚拟函数也可以inline 来提高效率了~~~

    3.静态成员函数

    class A{
    public:
        static int a;
        static int geta(){return a;}
    };
    int A::a=33333;
    int main(){
        cout<< ((A*)0)->geta()<<endl;
    }

    static的主要特征是他没有this 指针,这样导致“

    他不能直接存取其class中的nonstatic members

    他不能被声明为const volatile 或者virtual

    他不需要经由class object 才被调用 虽然大部分情况是这样调用的

    如果取一个static member func 地址则得到的是他在内存中的真正地址,而且得到的是一个函数指针,而不是一个指向class member 函数的指针

     

    ····虚拟成员函数

    为了支持virtual func 机制,必须首先能够对多态对象由某种形式的运行期类型判断方法

    c++中多态表示:以一个public blase class 指针或者引用 寻址出一个derived class object 的意思

    识别出哪个类需要支持多态只要看他是否有任何的virtual func

    ~~~单一继承

    vtable中每一个virtual func(包括pure func)都被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的virtual function 的关联

     

    当一个class 继承自上一个class时候

    1.可以继承base class 声明的virtual func ,这样该函数实体的地址会被拷贝到他的vtable相对应的slot 中,位置x不变 这样调用时候 ptr->func();会翻译成 (*ptr->vtbl[x])func(ptr) ;而不用管ptr 到底是一个base 还是一个derived

    2.他可以使用自己的函数实体,表示他自己的函数地址必须放在相应的位置x处 ,跟上面的例子一样

    3.可以加入新的virtual 函数,这时候vtbl 会变大

    ~~~多重继承呢

    多重继承时候 例如 Derived public 自 Base1,Base2

    Base2 *pbase2 = new Derived; 新的Derived必须调整

    Base2 *pbase2 = tmp?tmp+sizeof(Base1):0;

    当程序员删除pbase2指向的对象时指针必须再一次调整。上述的调整并不能在编译时期设定,因为pbase2指向的对象只有在执行期才能确定。

    同样道理,pbase2 如果要调用函数的话,调用操作会引发必要的指针调整,也必须在执行期调整。

    Bjarne采用扩充vtable 每一项记录调整this指针的信息,但浪费,因为大部分不需要调整

    Thunk技术是用一段汇编实现调整this指针以及跳到virtual func的过程

    调整this指针的第二个负担是:如果由derved class 调用,或者由第二个base class 调用,同一个函数可能在virtual table 对应多个slots

    pbase1 和derived 的vtable可以合并,他们用同样的slots 偏移,里面可以放真正的地址,而pbase2 需要调整this指针,其vtabl 相应的地址放的是相应的thunk地址。

    可以看到”:

    1.如果通过指向第二个base class 指针调用derived的func ptr 需要调整

    2.如果通过指向derived指针调用从第二个继承来的func 需调整

    3.如果允许virtual func 返回类型有所变化,可能base 可能derived,也需要调整this

    Microsoft 用address point 策略,即将用来改写别人的函数,期待获得的参数(this)是引入该class 的地址,这就是函数的address class(~~不了啊~~)

    ~~~虚拟继承下的virtual func

      即便只有一个base clas 它的布局转换也需要this 指针的调整,相当复杂~~~

    …指向成员函数的指针

    double Point::x();

    可以定义指向成员函数的指针

    double (Point::* pmf)()=&Point::x;

    调用可以  (origin.*pmf)() 或者 ptr->*pmf();

    如果是虚拟函数的指针呢??

    Point* ptr= new Point3d;

    如果x是一个虚拟函数

    (ptr->*pmf)();仍然是Point3d::x()被调用么?

    答案~~是的

    因为取得虚拟函数的地址其实取得的是虚拟函数的offset值

    调用会变成  (*ptr->vtbl[(int)pmf])(ptr);

    class A{
    public:
        static int a;
        static int geta()  {return a;}  //静态并不能作为重载条件
        int geta(int x){
            return a;
        }
         int  geta( int  a)const{} // const成员函数 ,可以作为重载条件
    };
    int A::a=33333;
    int main(){
        A a;
        cout<< ((A*)0)->geta()<<endl;//静态成员函数的一种调用方法 ((A*)0)->geta()
        int(*p)()= &A::geta;
        cout<<(*p)()<<endl;
        int (A::* pgeta)(int a) = &A::geta;
        cout<<(a.*pgeta)(3)<<endl;
    }

    输出均为33333 

    多重继承下呢????

    Microsoft提供了3种解决方法:

    一种:单一继承的情况(带vcall thunk地址或者函数地址)

    2多重继承 带有faddr 和delta

    虚拟继承 带有四个members

    (·····具体以后再查吧)

    ----------

    inline members

    真正的inline 函数扩展是在调用的那一个点上,这回带来参数的求值操作以及暂时性对象的管理

     

    形式参数 formal arguments

    在inline 期间 每一个形式参数都会被相应的实际参数取代,副作用是,不可以只是简单的一一封塞程序中出现的每一个形式参数,因为这将导致对于实际参数的多次求值操作,可能产生 带来副作用的 实际参数,通常这需要嵌入实际对象的~~~~

    所以,如果实际参数是常量,那么我们可以直接绑定,如果不是常量也没有副作用,我们直接代替,否则~~~暂时对象会需要的~·

    例如:

    inline int min(int i,int j) { return i<j ? i:j ;}

    minval = min(val1,val2);

    minval = min(11,12);

    minval = min (foo(),bar()+1);

     

    这会扩展成: minval = val1<val2 ? val1?val2;

    minval = 11;( 常量哦)

    int t1,t2; minval =(t1 = foo()), (t2=bar()+1),t1<t2?t1:t2;

    如果我们改变函数定义

    {int minval = i<j?i:j; return minval;}

    如下调用{int minval ; minval = min(val1,val2);}

    为了维护局部变量可能会变成:

    { int m_lv_minval; minval=(__min_lv_minval=val1<val2?val1:val2),min_lv_minval;}

    一般而言,inline 函数的每一个局部变量都必须放在函数调用的一个封闭区段中,拥有一个独一无二的名字,如果inline函数以单一表达式扩展多次,那么每次扩展都需要自己的一组局部变量。如果inline 函数可以以分离的多个式子被扩展多次,那么只需要一组局部变量就可以重复使用,因为他们被封闭在自己的scope中:

    例如 minval = min(val1,val2) + min(foo(),foo()+1) ;

    扩展 int __min_lv_minval_0,__min_lv_minval_1,t1,t2;

    minval = ((__min_lv_minval_0 = val1<val2?val1:val2),__min_lv_minval_0)+…);

    参数带有副作用或者是以一个单一表达式做多重调用,或者是在inline 函数内部有多个局部变量

    都会产生局部变量,要小心对待

    --------------------结束线哦~~~~~~··----------------------

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    posted @ 2010-03-09 22:20 rikisand 阅读(1566) | 评论 (1)编辑 收藏

    DATA 语义学

    这段代码输出什么?

    #include <iostream>
    using namespace std;
    class A{ public:A(){ac='s';}private:char ac;};
    class B:virtual public A{public:B(){a='e';}char a; };
    class C:virtual public A{ };
    class D:public  B,public  C
    {
    public:
        D():A(),B(),C(){b=13;}
        int b;
    };
    int main(){
        D d;
        cout<<"sizeof(A)="<<sizeof(A)<<endl;
        cout<<"sizeof(B)="<<sizeof(B)<<endl;
        cout<<"sizeof(C)="<<sizeof(C)<<endl;
        cout<<"sizeof(D)="<<sizeof(D)<<endl;
        cout<<endl;
        cout<<"address of A's subobject in d"<<(A*)&d<<endl;
        cout<<"address of B's subobject in d"<<(B*)&d<<endl;
        cout<<"address of C's subobject in d"<<(C*)&d<<endl;
        cout<<"address of D's subobject in d"<<(D*)&d<<endl;
        cout<<endl;
        int* p = (int*)(*((int*)&d));
        cout<<"address of b's virtual base table="<<p<<endl;
        cout<<"first member in b's virtual base table="<<*p<<endl;
        cout<<"second member in b's virtual base table="<<*(p+1)<<endl;
        cout<<"third member in b's virtual base table="<<*(p+2)<<endl;
        cout<<endl; 
        p= (int*)*((int*)((C*)&d));
        cout<<"address of c's virtual base class table= "<<p<<endl;
        cout<<"first member in c's virtual base table="<< *p<<endl;
        cout<<"second member in c's virtual base table="<<*(p+1)<<endl;
        cout<<"third member in c's virtual base table="<<*(p+2)<<endl;
        char *pchar= (char*)(&d)+4; //char型加4   注意A中的ac其实是私有变量,B不应该可以访问到的,实际上通过强制转换可以非法触及她-。-
        cout<<*pchar<<endl;
        cout<<*(pchar+12)<<endl;
        B b;
        int *pint =(int*)(&b)+1; //int型+1 
        cout<<*((char*)(pint))<<endl;
        pint = (int*)(&d)+3;
        cout<<*(pint)<<endl;
    }

     

    结果是:

    sizeof(A)=1
    sizeof(B)=9
    sizeof(C)=5
    sizeof(D)=17

    address of A's subobject in d0012FF74
    address of B's subobject in d0012FF64
    address of C's subobject in d0012FF6C
    address of D's subobject in d0012FF64

    address of b's virtual base table=00403350
    first member in b's virtual base table=0
    second member in b's virtual base table=16
    third member in b's virtual base table=0

    address of c's virtual base class table= 00403358
    first member in c's virtual base table=0
    second member in c's virtual base table=8
    third member in c's virtual base table=0
    e
    s
    e
    13

     

    1.语言本身造成的负担:如virtual baseclass

    2.对齐造成的负担(对齐会单独开题讨论)

    3.编译器优化处理 A虽然是空的 but 为了让A的两个object在内存中得到不同的地址,编译器给他加上了一个byte,但是B和C都没有这一个byte呢?这是编译器做了优化,允许省掉这一个byte

    看上面的代码”:

    环境是vs2008 对齐设置为4字节

    A的大小为1 因为有一个char 没有对齐因为不是结构型对象,如果A没有这个char大小依然是1的

    B的大小为9 首先开始是它的virtual base class ptr,4个字节的指针,然后是他自己的member 1个char 此时为了保证其对象完整性编译器对齐到4字节处也就是在第九个字节内放入基类的member

    这样如果我们把A=B B赋给A,传输可以从整4字节开始割出A即可

    C的大小是5 没什么好解释

    D的大小是17首先是B的8字节(4字节vbptr+1字节char member+3字节对齐)然后是C的4字节vbptr,然后是自己的member4字节最后是1字节的base class member,可以看到B和C的base class table中的项都是自己位置与这个member的offset 值

     

    不同编译器可能结果不同的,因为c++ standard 并没有强制规定 base class subobjects的顺序等

     

    data member 是程序执行过程中的某种状态:

    static data member 是整个class 的状态

    non-static data member 是个别class-object 的状态

    c++对象尽量以空间优化和存取速度的考虑来表现non-static members ,并且和c的struct 数据配置的兼容性。

    static data member 放在程序的一个global data segment 中,不会影响个别的class-object 大小

    ,在class没有object 时已经存在,但是template中有些不同

     

    -----DATA member 的绑定

    始终把nested type 声明 放在class 起始处,argument list 中的名称会在第一次遇到时候被适当的决议完成,因此extern 和nested type names 之间的非直觉绑定操作还是会发生。

    ---- DATA 的存取

    Point3d origin,*pt=&origin;

    origin.x = 0.0; 和 pt->x=0.0 ; 有什么区别么??

    如果x是静态data member 则完全没有区别 因为他们都在data segment 中和object无关

    ~nonstatic data member---------

    如果point3d不包含虚拟继承那么没有差异

    否则我们不能确定pt中必然指向哪一种因此不能在编译器确定offset需要一些运行时候的计算抉择,而origin则不同一定是某一个类型的所以没有问题

    多继承或者单继承都不会带来访问上的影响,因为他们都可以向c的结构体那样在编译时期确定各个member的offset。即使是多继承pt指向第二个baseclass的data,由于member的位置在编译时期就已经固定了,因此存取member只是一个offset运算,像单一继承那样简单,不管是指针,reference或者object都一样

    只有virtual base class 会带来一些损耗,因为他使得对象模型变得复杂了

    如果我们在一个类中加入了virtual func 会发生什么~~~

    1. 会产生一个virtual table,存放每一个virtual func地址以及rtti支持的type_info

    2.class object 内都加入一个vptr,提供执行期的链接

    3.加强ctor 设定vpr初值

    4.加强dtor 消除vptr 从dirived class 到 base class

     

    虚拟继承:

    他必须支持某种形式的“shared subobject”继承

    那么一个object会分成一个不变局部和一个共享局部的数据,共享局部就是virtual base class subobject,他的位置会因为每次派生操作发生变化(例如一个virtual base class subobject的位置在不同级别的继承体系中的位置是不确定的,不像多继承单继承那样有固定的offset),所以他只能间接存取,因此产生了效率缺损.(间接是指的是他只能首先读出他的指针,然后根据指针的内容取到他)

    所以在虚拟继承基类中最好不要有data member

    -------指向DATAmember 的指针

    #include <iostream>
    using namespace std;

    class  A
    {
    public:

        int a;
    };
    int main(){
        int  A::* p=&A::a;
        cout<<p<<endl;
    }

    输出 1

    因为为了防止&A::a和 int A::*a = 0;一样把他加了1。

     

    虚拟继承带来的主要冲击是,妨碍了优化的有效性,因为每一层虚拟继承都带来了一个额外层次的间接性,在编译器中存取 类似point::x的操作pb.*bx

    会被转化为 &pb->vbcPoint+bx

    而不是转换成 &pB +bx

    额外的间接性会降低“把所有操作都搬移到缓存器中执行”优化能力,也就是降低了局部性~

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    posted @ 2010-03-07 16:39 rikisand 阅读(297) | 评论 (0)编辑 收藏

    构造函数语义学

    --了解编译器在构造对象时背着我们干了什么勾当

    Default Ctor 在需要的时候被构建出来~

    什么需要? 是编译器需要而不是程序的需要,所以不要期望编译器生成的Ctor会帮我们把我们的成员变量初始化为零。那是程序的需要,也就是我们程序员的任务····

    例如:

          class Foo(public:int val;);

          void foo_bar(){Foo bar; if(bar.val)dosth;} 不要期望编译器帮我们初始化它为0。只有global变量会初始化为0,初始化val这样的变量需要我们自己写代码的~~

    Default Ctor分为trival(没用的)和non-trival的,下面讨论什么时候编译器需要ctor 也就是有用的ctor,这时候如果我们没有提供一个默认的default ctor 它会帮我们合成一个的~~

    1.带有Default Ctor 的member class object

    很好理解,既然内部成员含有default ctor 那么我们创建新的对象时需要调用它,而我们并木有调用它的函数,编译器自然会帮我们提供一个。如果我们提供了default ctor ,那么编译器会在我们提供的函数加入调用必须调用的member class 的default ctor。同样的在每一个ctor里面都将在用户写的代码之前调用需要调用的default ctor -------看例子:

    class Dopey();class Sneezy{public:Sneezy(int val);Sneezy();};class Bashful{public:BashFul();};

    class Snow_white{public: Dopey dopey;Sneezy sneezy;Bashful bashful;private:int muble};

    如果Snow_white没有定义default ctor 那么编译器会创建一个,并在其中依照声明顺序依次调用dopey sneezy bashful的default ctor 然而如果:

    Snow_white::Snow_white():sneezy(1024){muble=2045;}

    编译器会扩张为

    Snow_white::Snow_white():sneezy(1024){

      dopey.Dopey::Dopey();

      sneezy.Sneezy::Sneezy(1024);

      bashful.Bashful::Bashful();

      /////////   explicit user code

      muble = 2048;

    }

    2.派生自带有Default ctor 的 base class

    同样道理如果父类有Default ctor 子类当然要调用,编译器会为想上面一样为我们加上

    3.含有virtual functions的Class

    创建object 时候需要ctor 来设置好vptr

    4.带有virtual base class

    virtual base 实现方法各有不同,然而共同点是必须是virtual base class 在其每一个derived class中的位置能够与执行期准备妥当

    X A B C 菱形继承

    void foo(const A*pa){pa->i=1024;}

    foo(new A);foo(new C);

    知道pa 后 i在对象中的位置并不是固定的,而是在运行时真正确定pa指向什么对象A还是C才能确定的,因此需要设定一个指向基类subobject的指针,所以需要ctor工作了

    OK:

    1.任何class 如果没有定义Default ctor 就会被合成一个

    2.合成出来的Default ctor 会明确设定每一个data member值

    错的很easy了~

    -------------------------------------------------------------------------------

    再来看 Copy Ctor:

    copy ctor 负责用另一个对象初始化一个对象

    operator = 负责用另一个对象给一个对象赋值

    直接赋值时,传参时,返回时可能调用Copy ctor

    Default member initialization~~~

    也就是memberwise 的initialization

    他会把每一个data member (内建的或者派生的)从一个object 拷贝到另一个object中去

    如果object允许bitwise的拷贝那么编译器就不用生成一个nontrival的default copy ctor

    什么时候不可以呢~

    1 内含一个member object 而后者含有copy constructor (声明或者合成的)

    2  继承一个base class 后者有copy ctor

    3  含有virtual func

    4  派生自一个继承链,其中有virtual base class

    1和2 中编译器会把member 或者baseclass copy ctor 调用安插在合成的copy ctor 中

    3 中:

    如果两个同样类型的object赋值时,没有问题因为他们的vptr相同

    但是考虑子类赋值给父类,此时vptr需要更改,那么此时不具有bitwise特性,因此需要编译器来加入语句正确更新vptr

    4中:

    每个编译器都承诺必须让derived class 中的virtual base class object 在执行期间准备妥当,维护位置完整性是编译器的责任。bitwise copy 有可能会破坏这个位置所以编译器需要在自己合成的copy ctor 中作出仲裁

    同样问题发生在继承体系中子类向父类赋值时,由于对象模型问题,直接bitwise复制可能会导致base class object 的破坏(后面章节会有讨论)

    --------------------------------------------------------------------------------

    程序转化语义学:

    X x0;

    void foo(){X x1(x0); X x2=x0; X x3=X(x0);}

    转化:重写定义,初始化操作会被剥除   copy constructor 调用会被安插

    void foo(){ X x1;X x2; X x3;  x1.X::X(x0); x2.X::X(x0); x3.X::X(x0);}

     

    参数的初始化:

    一种策略导入暂时的object

    void foo(X x0);X xx; foo(xx);

    转化:

    X _tmp;

    _tmp.X::X(x0); foo(_tmp);

    foo变成 void foo(X& x0);

    另一种是拷贝构建:

    把实际参数直接建构造应该在的位置上,函数返回时局部对象的destructor 会执行

    也就是说把x0建构在foo 的函数执行的地方

     

    返回值的初始化:

    X bar(){

        X xx;

        return xx;

    }

    返回值如何从局部对象xx拷贝而来呢?

    一种方法:1.加上额外参数,类型是class object的reference,这个参数用来放置被拷贝建构的返回值 (注意拷贝建构也就是说他被放在应该在的位置,也就是说不是局部变量了)

    2.return 指令之前安插一个copy constructor 调用操作 ,以便将传回的object 内容当做上述参数的初值

    so 上面的程序变成了:

    void bar(X& _result){

      X xx;

      xx.X::X(); //编译器产生的default ctor 调用

      _result.X::X(xx);//编译器产生的copy ctor 调用

      return ;

    }

    现在编译器必须转换bar的调用操作 X xx=bar();转换成 X xx; bar(xx); // 注意不用copy ctor了直接操作在xx上了 如果编译器做了优化 这就是named return value 优化

    而:

    bar.memfunc();//bar()传回的X 调用成员函数

    变成:

    X _tmp;  (bar(_tmp),_tmp).memfunc();

    同样道理函数指针 X(*pf)(); 变成 void (*pf)(X&);

    使用者的优化:

    X bar(const T& y,const T& z){

        X xx;

        通过 y z 计算xx

       return xx;

    }

    这种情况下要iuxx被memberwise拷贝到编译器产生的_result中,

    如果定义 ctor来利用yz计算xx则:

    X bar(const T& y,const T& z){

         return X(y,z);

    }

    变成:

    bar(X& result){

         result . X::X(y,z);

         return;

    }

    无需copy ctor了

     

    编译器的优化:

    如上所述bar中返回了具名数值 named value,因此编译器有可能自己优化,方法是以result取代named return value

    bar(X& result){_result.X::X(); 直接处理result 并不处理变量xx然后复制给result 这样就优化了}

    这个优化的激活需要class提供一个copy ctor~~~~~~

     

    Copy ctor 要不要:

    如果一个class 符合bitwise的要求那么此时member wise 的拷贝已经高效简洁 无需加入了

    但是如果class需要大量的member wise 初始化操作,如用传值方式返回objects,如果是这样提供一个copy ctor 可以激活nRV named return value 优化,这就很合理了

     

    成员们的初始化过程:

    什么时候必须用初始化列表:

    1.初始化一个reference时 2.初始化一个const member 3.调用父类ctor而且有参数时4调用member class ctor 有参数

    其他情况呢:

    class word{string name;int cnt;public: name=0;cnt=0;}

    编译器可能这么做:

    word::word{

        name.String::string();调用string的default ctor

        string tmp=string(0);

    _name.string::operator=(tmp);

    tmp.string::~string();

    cnt=0;

    }

    显然name放到初始化列表会更有效率 ,会变成

    name.String::String(0);

    而cnt这种内建类型则没有关系,放不放到初始化列表没有效率上的差别

    初始化列表究竟让编译器做了什么????

    编译器会一个个操作list中的式子,以适当次序在ctor内安插初始化操作在任何explicit user code 之前。

    注意的地方:

           list中的次序是按照members声明次序决定而不是list 中的排列顺序决定。

    例如:class x{int i;int j; X(int val):j(val),i(j)}

    错了 i先声明则i首先赋予val 然后用未初始化的j赋给i。。。

    可以这样X::X(int val):j(val){i=j;}

    由于会安插在explicit code 之前 所以没问题 会变成 j=val;   i=j;

    可否用member functions 初始化成员??

    答案是可以的,因为和objects相关的this指针已经构建妥当,只是要注意函数调用的member是否已经构建妥当了即可

    ------         -  -  - ----------       --       --疲惫的结束线-          - -  -     - --            -           -----

    name return value TEST:

    ~~~~1 cl /od 不开优化

    #include <iostream>
    using namespace std;
    class RVO
    {
    public:

        RVO(){printf("I am in constructor\n");}
        RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
        ~RVO(){printf ("I am in destructor\n");}
        int mem_var;      
    };
    RVO MyMethod (int i)
    {
        RVO rvo1;
        rvo1.mem_var = i;
        return (rvo1);
    }
    int main()
    {
        RVO rvo;rvo=MyMethod(5);
    }

    输出:

    I am in constructor         //rvo 创建
    I am in constructor         // rvo1创建
    I am in copy constructor // rvo1赋值给hiddern
    I am in destructor          // rvo1解构
    I am in destructor          // hiddern解构
    I am in destructor         //  rvo 解构

    A MyMethod (A &_hiddenArg, B &var)
    {
       A retVal;
       retVal.A::A(); // constructor for retVal
       retVal.member = var.value + bar(var);
       _hiddenArg.A::A(retVal);  // the copy constructor for A
       return;
    retVal.A::~A();  // destructor for retVal
    
    }
    A MyMethod(A &_hiddenArg, B &var)
    {
       _hiddenArg.A::A();
       _hiddenArg.member = var.value + bar(var);
       Return
    }
    ~~~~2 cl /o2 代码同上
    output

    I am in constructor  //rvo创建
    I am in constructor  //hiddern 创建
    I am in destructor   //hiddern 解构
    I am in destructor   //rvo解构

    我不明白的是hiddern 怎么传给rvo ,我猜可能是编译器按照bitwise的复制方式进行的,此时编译器并没有直接建构结果于rvo上 ,看看下面的试验:

    注:明白了, 结果直接建构在hiddern,然后通过operator = 传给rvo 。没有copy ctor因为拷贝构造函数是负责初始化的,而operator = 才是用来赋值的.

    经过代码证明是对的,如果重载赋值运算符 输出变成:

    I am in constructor  //rvo创建
    I am in constructor  //hiddern 创建

    I am in operator =   //赋值操作~~
    I am in destructor   //hiddern 解构
    I am in destructor   //rvo解构

    ~~~~3 cl /od

    #include <iostream>
    using namespace std;
    class RVO
    {
    public:

        RVO(){printf("I am in constructor\n");}
        RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
        ~RVO(){printf ("I am in destructor\n");}
        int mem_var;      
    };
    RVO MyMethod (int i)
    {
        RVO rvo1;
        rvo1.mem_var = i;
        return (rvo1);
    }
    void abc(RVO& i){
    }
    int main()
    {
        RVO rvo=MyMethod(5);  //此时定义和赋值放到了一个表达式子
        return 0;
    }

    output:

    I am in constructor           // rvo1 创建 注意 跟上面的第一种情况下的hiddern一样 rvo并没有调用ctor
    I am in copy constructor   // rvo1 拷贝构造给rvo 此时没有hiddern了 直接构建rvo了
    I am in destructor            // rvo1 析构
    I am in destructor            // rvo1 解构

    ~~~~3 cl /o2  再来~~~~ NRV出马

    I am in constructor           // rvo构建了 
    I am in destructor            // rvo析构了

    此时 mymethod中的一切都直接反映在rvo身上

    ok~~~~~4个代码完全一样构造析构拷贝函数个数由2-6不等~~~over~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    posted @ 2010-03-03 22:23 rikisand 阅读(1374) | 评论 (4)编辑 收藏

    寒假基本读了一遍,现在再读一遍,做下笔记。

    笔记当做的精炼而有意义,而后回顾可知其意,回其味,方有成效

    ---------------------------------------------------------------------------------

    C语言:数据和操纵数据的方法分开声明,方法被写成函数处理外部数据

    C++:数据和方法的组合封装ADT 更安全 支持继承 清晰

    封装在空间上的代价:

    如果仅仅是普通继承则并没有什么代价,因为每个数据成员直接内涵在每一个class对象中,和c中的struct一样,而member function如果不是inline的则只会产生一个实体,而inline会在每一个使用的模块产生一个函数实体

    c++在空间和存取效率的代价主要由virtual带来:

    virtual function 机制: vtable vptr 执行期绑定

    virtual class 机制:

    还有多重继承下的额外负担(子类转换成第二个父类的时候)

    ----------------------------------------------------------------------

    c++对象模型:也就是我们怎么在内存中安排各个数据成员以及成员函数

    1.简单对象模型~ 对象由各个slot组成,每一个slot包含指针,指向各个成员

    2.表格驱动模型~ 对象含有两个指针,一个指向成员数据表,另一个指向成员函数表

    3.c++实际对象模型~ 非静态数据被放在对象内,而静态数据成员放在对象外(显然啊,他属于类),成员函数

      不管静态或者非静态都放在对象外面

      Virtual functions :每个class产生一堆指向vitrualfunctions的指针,放在vtables中,每个对象

      含有vptr 指向vtable vptr的设定,重置均有class的ctor,dtor,copy assignment 运算符自动完成

      优点:空间和存储效率 缺点:如果对象模型的nonstatic data members 有所修改,也就是对象内部布局有所更改,那么使用该对象的应用程序需要重新编译.

    虚拟继承:不管基类被派生了多少次,永远只有一个实体(菱形继承最下面对象中只有一个顶部类实体)

    继承的实现:一种可以用basetable 在basetable中指向一个baseclass的地址,但这会随着继承深度越来越深变得慢

    现在采用的方法之后记录~~~ :P

    书中的程序:对象模型对程序的影响:

    X foobar(){
        X xx;
        X *px = new X;
        xx.foo();
        px->foo(); //foo是一个virtual function
        delete px;
        return xx;
    }
    //调用处 X _result;
    //      foobar(_result);
    void foobar(X& _result){
        _result.X::X();//构造result
        px = _new(sizeof(X));
        if(px!=0)
            px->X::X;
        foo(&_result);//使用result取代xx但是不激活virtual机制
        (*px->vtbl[2])(px);//使用virtual机制扩展px->foo();
        if(px!=0){
            (*px->vtbl[1])(px);//使用virtual调用destructor
            delete px;
        }
        //不用使用named return statement
        return ;
    }

    ------------------------------------------------------------------------------------

    关键词带来的麻烦~~

    struct 和 class 在C++中可以混用,并不是由于使用哪一个决定其特性,只是给人的感觉不同罢了class更像是c++中继承的代名词 struct更像C中的数据集

    如果需要向C函数传递数据集合可以使用struct以保证对象模型的支持

    使用struct完全可以声明各种class的特性

    对象的差异

    1.程序模型procedural model  同c一样

    2.抽象数据类型模型 ADT object-base 抽象指的是一族表达式(public接口)的提供,具体计算方法未明

    3.面向对象 oo 有一些彼此相关的模型通过一个抽象的baseclass用以提供公共接口封装起来

     

    OO中程序员可能需要处理一个未知实体,在执行点之前无法确定,这是由pointers和reference实现的而在ADT中程序员处理的是一个拥有固定单一类型的实体,编译时期就已经确定了~

    c++中的多态:1.把derived class的指针转化成一个基类的指针 2virtualfunctions 3.dynamic_cast 和typeid运算符

    多态主要是经由一个共同的接口来影响类型的封装,这个接口通常被放置在抽象的baseclass中,这个共享接口是以virtual functions机制来引发的,可以在执行期间根据object的真正类型解析出到底是哪一个函数实体被调用。

    我们的代码可以避免由于增加某一个特定的derived 实体而改变,只需要新的derivedclass重写这样的接口便可。

    //测试指针引用的多态
    #include <iostream>
        using namespace std;
        struct Point{
        public:
            Point(float x=0.):_x(x){}
            float x(){return _x;}
            void x(float xval){_x=xval;}
            virtual void  out(){
                cout<<"I am a Point"<<endl;
            }
        protected:
            float _x;
        };
        struct Point2d:public Point{
        public:
            Point2d(float x=0.,float y=0.):Point(x),_y(y){}
            float y(){return _y;}
            void y(float yval){_y=yval;}
            virtual void out(){
                cout<<"I am a Point2d"<<endl;
            }
        protected:
            float _y;
        };
        int main(){
            Point pt;
            Point2d pt2;
            Point* ptr;
            Point2d* ptr2;
            pt.out(); pt2.out();
            pt=pt2;pt.out();
            ptr=&pt2;
            ptr->out();
            (*ptr).out();
            Point& ptref=pt2;
            ptref.out();
        }

    output:

    I am a Point
    I am a Point2d
    I am a Point
    I am a Point2d
    I am a Point2d
    I am a Point2d

    指针和引用可以实现多态,因为指针和引用的赋值只是改变其指向的范围,并没有触及对象模型的改变,因此可以产生多态的效果。而value的赋值操作会导致对象模型的切割,从而真实的改变了对象的内部模型与类型,失去了多态的效果。oo 不支持对对象的直接处理

     

    指针的类型:

    不同的指针从内存角度来看没什么不同,只是占据了一个word的空间,但是指针的类型会告诉编译器如何解释某个特定地址中的内存内容以及大小

     

    OB设计也就是ADT设计比OO设置更有效率因为其所有函数引发操作均在编译器确定OO需要一些运行时确定,对象构建起来不需要设置virtual机制

    比OO紧凑因为无需支持virtual机制从而没有额外负担

    但是OB相对来说弹性较小~~

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    posted @ 2010-03-03 13:58 rikisand 阅读(1926) | 评论 (0)编辑 收藏

    ----节录自清华电机彭明辉老师于系刊发表的文章
    许多同学应该都还记得联考前夕的焦虑:差一分可能要掉好几个志愿,甚至于一生的命运从此改观!到了大四,这种焦虑可能更强烈而复杂:到底要先当兵,就业,还是先考研究所?我就经常碰到学生充满焦虑的问我这些问题。可是,这些焦虑实在是莫须有的!生命是一种长期而持续的累积过程,绝不会因为单一的事件而毁了一个人的一生,也不会因为单一的事件而救了一个人的一生。属于我们该得的,迟早会得到;属于我们不该得的,即使侥幸巧取也不可能长久保有。如果我们看清这个事实,许多所谓"人生的重大抉择"就可以淡然处之,根本无需焦虑。而所谓"人生的困境",也往往当下就变得无足挂齿。
      我自己就是一个活生生的例子。从一进大学就决定不再念研究所,所以,大学四年的时间多半在念人文科学的东西。毕业后工作了几年,才决定要念研究所。硕士毕业后,立下决心:从此不再为文凭而念书。谁知道,世事难料,当了五年讲师后,我又被时势所迫,出国念博士。
      出国时,一位大学同学笑我:全班最晚念博士的都要回国了,你现在才要出去?两年后我从剑桥回来,觉得人生际遇无常,莫此为甚:一个从大一就决定再也不钻营学位的人,竟然连硕士和博士都拿到了!属于我们该得的,哪样曾经少过?而人生中该得与不该得的究竟有多少,我们又何曾知晓?从此我对际遇一事不能不更加淡然。
      当讲师期间,有些态度较极端的学生会当面表现出他们的不屑;从剑桥回来时,却被学生当做不得了的事看待。这种表面上的大起大落,其实都是好事者之言,完全看不到事实的真相。从表面上看来,两年就拿到剑桥博士,这好像很了不起。但是,在这"两年"之前我已经花整整一年,将研究主题有关的论文全部看完,并找出研究方向;而之前更已花三年时间做控制方面的研究,并且在国际着名的学术期刊中发表论文。而从硕士毕业到拿博士,期间七年的时间我从不停止过研究与自修。所以,这个博士其实是累积了七年的成果,或者,只算我花在控制学门的时间,也至少有五年),根本也没什么好惊讶的。
      常人不从长期而持续的累积过程来看待生命因积蓄而有的成果,老爱在表面上以断裂而孤立的事件夸大议论,因此每每在平淡无奇的事件上强做悲喜。可是对我来讲,当讲师期间被学生瞧不起,以及剑桥刚回来时被同学夸大本事,都只是表象。事实是:我只在乎每天二十四小时点点滴滴的累积。
      拿硕士或博士只是特定时刻里这些成果累积的外在展示而已,人生命中真实的累积从不曾因这些事件而终止或添加。
      常有学生满怀忧虑的问我:"老师,我很想先当完兵,工作一两年再考研究所。这样好吗?"
      "很好,这样子有机会先用实务来印证学理,你念研究所时会比别人了解自己要的是什么。"
      "可是,我怕当完兵又工作后,会失去斗志,因此考不上研究所。"
      "那你就先考研究所好了。"
      "可是,假如我先念研究所,我怕自己又会像念大学时一样茫然,因此念的不甘不愿的。"
      "那你还是先去工作好了!"
      "可是。。。。。。。
      我完全可以体会到他们的焦虑,可是却无法压抑住对于这种话的感慨。其实,说穿了他所需要的就是两年研究所加两年工作,以便加深知识的深广度和获取实务经验。
      先工作或先升学,表面上大相迳庭,其实骨子里的差别根本可以忽略。
      在"朝三暮四"这个成语故事里,主人原本喂养猴子的橡实是"早上四颗下午三颗",后来改为"朝三暮四",猴子就不高兴而坚持改回到"朝四暮三"。其实,先工作或先升学,期间差异就有如"朝三暮四"与"朝四暮三",原不值得计较。但是,我们经常看不到这种生命过程中长远而持续的累积,老爱将一时际遇中的小差别夸大到生死攸关的地步。 
      最讽刺的是:当我们面对两个可能的方案,而焦虑得不知如何抉择时,通常表示这两个方案可能一样好,或者一样坏,因而实际上选择哪个都一样,唯一的差别只是先后之序而已。而且,愈是让我们焦虑得厉害的,其实差别越小,愈不值得焦虑。反而真正有明显的好坏差别时,我们轻易的就知道该怎么做了。可是我们却经常看不到长远的将来,短视的盯着两案短期内的得失:想选甲案,就舍不得乙案的好处;想选乙案,又舍不得甲案的好处。如果看得够远,人生长则八、九十,短则五、六十年,先做哪一件事又有什么关系?甚至当完兵又工作后,再花一整年准备研究所,又有什么了不起?当然,有些人还是会忧虑说:"我当完兵又工作后,会不会因为家累或记忆力衰退而比较难考上研究所?"我只能这样回答:"一个人考不上研究所,只有两个可能:或者他不够聪明,或者他的确够聪明。不够聪明而考不上,那也没什么好抱怨的。假如你够聪明,还考不上研究所,那只能说你的决心不够强。假如你是决心不够强,就表示你生命中还有其他的可能性,其重要程度并不下于硕士学位,而你舍不得丢下他。既然如此,考不上研究所也无须感到遗憾。不是吗?"人生的路这么多,为什么要老斤斤计较着一个可能性?
      我高中最要好的朋友,一生背运:高中考两次,高一念两次,大学又考两次,甚至连机车驾照都考两次。毕业后,他告诉自己:我没有关系,也没有学历,只能靠加倍的诚恳和努力。现在,他自己拥有一家公司,年收入数千万。
      一个人在升学过程中不顺利,而在事业上顺利,这是常见的事。有才华的人,不会因为被名校拒绝而连带失去他的才华,只不过要另外找适合他表现的场所而已。反过来,一个人在升学过程中太顺利,也难免因而放不下身段去创业,而只能乖乖领薪水过活。福兮祸兮,谁人知晓?我们又有什么好得意?又有什么好忧虑?人生的得与失,有时候怎么也说不清楚,有时候却再简单不过了:我们得到平日累积的成果,而失去我们不曾努力累积的!所以重要的不是和别人比成就,而是努力去做自己想做的。最后该得到的不会少你一分,不该得到的也不会多你一分。
      好像是前年的时候,我遇到一位高中同学。他在南加大当电机系的副教授,被清华电机聘回来开短期课程。从高中时代他就很用功,以第一志愿上台大电机后,四年都拿书卷奖,相信他在专业上的研究也已卓然有成。回想高中入学时,我们两个人的智力测验成绩分居全学年第一,第二名。可是从高一我就不曾放弃自己喜欢的文学,音乐,书法,艺术和哲学,而他却始终不曾分心,因此两个人在学术上的差距只会愈来愈远。反过来说,这十几二十年我在人文领域所获得的满足,恐怕已远非他能理解的了。我太太问过我,如果我肯全心专注于一个研究领域,是不是至少会赶上这位同学的成就?我不这样想,两个不同性情的人,注定要走两条不同的路。不该得的东西,我们注定是得不到的,随随便便拿两个人来比,只看到他所得到的,却看不到他所失去的,这有什么意义?
      有次清华电台访问我:"老师你如何面对你人生中的困境?"我当场愣在那里,怎么样都想不出我这一生什么时候有过困境!后来仔细回想,才发现:我不是没有过困境,而是被常人当作"困境"的境遇,我都当作一时的际遇,不曾在意过而已。刚服完兵役时,长子已出生却还找不到工作。我曾焦虑过,却又觉得迟早会有工作,报酬也不至于低的离谱,不曾太放在心上。念硕士期间,家计全靠太太的薪水,省吃俭用,对我而言又算不上困境。一来精神上我过的很充实,二来我知道这一切是为了让自己有机会转行去教书(做自己想做的事)。三十一岁才要出国,而同学正要回系上任教,我很紧张(不知道剑桥要求的有多严),却不曾丧气。因为,我知道自己过去一直很努力,也有很满意的心得和成果,只不过别人看不到而已。  
      我没有过困境,因为我从不在乎外在的得失,也不武断的和别人比高下,而只在乎自己内在真实的累积。  
      我没有过困境,因为我确实了解到:生命是一种长期而持续的累积过程,绝不会因为单一的事件而有剧烈的起伏。  
      同时我也相信:属于我们该得的,迟早会得到;属于我们不该得的,即使一分也不可能增加。假如你可以持有相同的信念,那么人生于你也会是宽广而长远,没有什么了不得的"困境",也没有什么好焦虑的了。
    注:清华=台湾清华.研究所=研究生


    posted @ 2010-03-01 19:36 rikisand 阅读(101) | 评论 (0)编辑 收藏

    仅列出标题
    共5页: 1 2 3 4 5