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的改变产生依赖

  • 相关阅读:
    leetcode 190 Reverse Bits
    vs2010 单文档MFC 通过加载位图文件作为客户区背景
    leetcode 198 House Robber
    记忆化搜索(DP+DFS) URAL 1183 Brackets Sequence
    逆序数2 HDOJ 1394 Minimum Inversion Number
    矩阵连乘积 ZOJ 1276 Optimal Array Multiplication Sequence
    递推DP URAL 1586 Threeprime Numbers
    递推DP URAL 1167 Bicolored Horses
    递推DP URAL 1017 Staircases
    01背包 URAL 1073 Square Country
  • 原文地址:https://www.cnblogs.com/shit/p/3268178.html
Copyright © 2011-2022 走看看