zoukankan      html  css  js  c++  java
  • effectiveC++ 内存管理 学习笔记


    1.尽量使用初始化列表而不要再构造函数里赋值,初始化顺序和声明的顺序一致,一些类型如const,引用等,必须使用初始化。对于非内部数据类型成员对象应当采用初始化表,以获取更高的效率。
    example:
    B::B(const A& a):m_a(a){}只调用了类A的拷贝构造函数
    2.基类都使用虚析构函数,这样才能在使用多态时,准确的析构派生类
    3.operator>>和operator<<决不能是成员函数。如果f是operator>>或operator<<,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。在类的内部,它可以用于静态和非静态成员。
    4.尽可能使用const.const关键字实在是神通广大。在类的外面,它可以用于全局或名字空间常量,以及静态对象(某一文件或程序块范围内的局部对象)。在类的内部,它可以用于静态和非静态成员.
    对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const,还有,两者都不指定为const:
    char *p = "hello"; // 非const指针,
    // 非const数据
    const char *p = "hello"; // 非const指针,
    // const数据
    char * const p = "hello"; // const指针,
    // 非const数据
    const char * const p = "hello"; // const指针,
    // const数据
    5.尽量用“传引用”而不用“传值”
    “通过值来传递一个对象”的具体含义是由这个对象的类的拷贝构造函数定义的。这使得传值成为一种非常昂贵的操作。
    为避免这种潜在的昂贵的开销,就不要通过值来传递对象,而要通过引用:
    const student& returnstudent(const student& s)
    { return s; }
    这会非常高效:没有构造函数或析构函数被调用,因为没有新的对象被创建。

    6.返回值:对于赋值函数,应当用“引用传递”的方式返回对象,对于相加函数应当用值传递,因为引用对象在函数结束时被销毁。

    例如:

    class String  
    {…  
        // 赋值函数  
        String & operate=(const String &other);   
            // 相加函数,如果没有friend修饰则只许有一个右侧参数  
            friend  String   operate+( const String &s1, const String &s2);   
    private:  
        char *m_data;   
    }  
      
    //String的赋值函数operate = 的实现如下:  
    String & String::operate=(const String &other)  
    {  
        if (this == &other)  
            return *this;  
        delete m_data;  
        m_data = new char[strlen(other.data)+1];  
        strcpy(m_data, other.data);  
        return *this;   // 返回的是 *this的引用,无需拷贝过程  
    }  

    对于赋值函数,应当用“引用传递”的方式返回String对象。如果用“值传递”的方式,虽然功能仍然正确,但由于return语句要把 *this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,降低了赋值函数的效率。例如:

    String a,b,c;

    a = b; // 如果用“值传递”,将产生一次 *this 拷贝
    a = b= c; // 如果用“值传递”,将产生两次 *this 拷贝
    对于相加函数,应当用“值传递”的方式返回String对象。如果改用“引用传递”,那么函数返回值是一个指向局部对象temp的“引用”。由于temp是在栈上申请的变量,函数执行完毕后被销毁,将导致返回的“引用”无效。

    //String的相加函数operate + 的实现如下:  
    String  operate+(const String &s1, const String &s2)    
    {  
        String temp;  
        delete temp.data;   // temp.data是仅含‘’的字符串  
        temp.data = new char[strlen(s1.data) + strlen(s2.data) +1];  
        strcpy(temp.data, s1.data);  
        strcat(temp.data, s2.data);  
        return temp;  
    }  

    如果函数返回值是一个对象,要考虑return语句的效率。例如
    return String(s1 + s2);
    这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建一个局部对象temp并返回它的结果”是等价的,如
    String temp(s1 + s2);
    return temp;
    实质不然,上述代码将发生三件事。首先,temp对象被创建,同时完成初始化;然后拷贝构造函数把temp拷贝到保存返回值的外部存储单元中;最后,temp在函数结束时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。

    7.划分全局名字空间的好处
    例如,假设library1.h定义了一些常量,其中包括:

    const double lib_version = 1.204;

    类似的,library2.h也定义了:

    const int lib_version = 3;

    很显然,如果某个程序想同时包含library1.h和library2.h就会有问题。

    要这么做:

    namespace sdm {
      const double book_version = 2.0;
      class handle { ... };
      handle& gethandle();
    }

    用户于是可以通过三种方法来访问这一名字空间里的符号:将名字空间中的所有符号全部引入到某一用户空间;将部分符号引入到某一用户空间;或通过修饰符显式地一次性使用某个符号:

    void f1()
    {
      using namespace sdm;           // 使得sdm中的所有符号不用加
                                     // 修饰符就可以使用
    
      cout << book_version;          // 解释为sdm::book_version
      ...
    
      handle h = gethandle();        // handle解释为sdm::handle,
                                     // gethandle解释为sdm::gethandle
      ...                            
    
    }
    
    void f2()
    {
      using sdm::book_version;        // 使得仅book_version不用加
                                     // 修饰符就可以使用
    
      cout << book_version;           // 解释为
                                      // sdm::book_version
      ...
    
      handle h = gethandle();         // 错误! handle和gethandle
                                      // 都没有引入到本空间
      ...                             
    
    }
    
    void f3()
    {
      cout << sdm::book_version;      // 使得book_version
                                      // 在本语句有效
      ...                             
    
      double d = book_version;        // 错误! book_version
                                      // 不在本空间
    
      handle h = gethandle();         // 错误! handle和gethandle
                                      // 都没有引入到本空间
      ...                            
    
    }

    8.将文件间的编译依赖性降至最低
    假设某一天你打开自己的C++程序代码,然后对某个类的实现做了小小的改动。提醒你,改动的不是接口,而是类的实现,也就是说,只是细节部分。然后你准备重新生成程序,心想,编译和链接应该只会花几秒种。毕竟,只是改动了一个类嘛!于是你点击了一下"Rebuild",或输入make(或其它类似命令)。然而,等待你的是惊愕,接着是痛苦。因为你发现,整个世界都在被重新编译、重新链接!
    在name.h中定义
    class name{};
    在person.h中:

    #include"name.h"
    class person{
    public:
    person(const name& _pname);
    virtual ~Person();
    private:
    name _pname;//实现细节
    string namestr() const;
    };

    在其他文件中:
    class man:public person{
    ...
    };
    class woman:public person{
    ...
    };

    这时候person文件和name.h之间建立了编译依赖关系,如果name改变了它的实现,或者name依赖的类改变了实现,包含person类的文件以及任何使用了person类的文件就必须重新编译。这时person,man,和woman的文件都要重新编译。
    为了实现定义与实现细节分开,我们可以这样定义person:

    class name;//提前声明
    class person{
    public:
    person(const name& _pname);
    virtual ~Person();
    private:
    string namestr() const;
    };

    如果这样做可行,person的用户就不需要重新编译,只可惜用起来才知道:

    int main()
    {
    int x;//定义一个int
    person p(new name);//定义一个person
    }

    当看到x的定义时,编译器知道必须为它分配一个int大小的内存。这没问题,每个编译器都知道一个int有多大。然而,当看到p的定义时,编译器虽然知道必须为它分配一个person大小的内存,但怎么知道一个Person对象有多大呢?
    对应于上面的代码,或许可以这样做:

    int main()
    {
      int x;                     // 定义一个int
      Person *p;                 // 定义一个Person指针,编译器知道是4字节
      ...
    }

    下面具体介绍怎么采用这一技术来实现person接口和实现的分离:

    class name;//提前声明
    class man;
    class woman;
    class person{
    public:
    person(const name& _pname);
    virtual ~Person();
    man *personimpl1;//指向具体实现类
    woman *personimpl2;
    string namestr() const;
    };
    //在person.cpp中:
    #include"person.h"
    #include"man.h"
    person.cpp::person(const name& _pname)
    {
    personimpl1 = new man(_pname);
    }
    string person::namestr()const
    {
    return personimpl1->name();
    }

    这样,person作为接口与实现完全分离。编译时不对name的改变产生依赖

  • 相关阅读:
    复杂报表的存储过程
    Jquery中使用setInterval和setTimeout
    Jquery EasyUi实战教程布局篇
    枚举enum
    myGeneration代码生成器
    带有分页的存储过程
    应用临时表的存储过程
    缓存类的写法
    HDU4706 Children's Day
    HDU4706 Children's Day
  • 原文地址:https://www.cnblogs.com/shit/p/3268178.html
Copyright © 2011-2022 走看看