zoukankan      html  css  js  c++  java
  • Effective C++ 之 Item 4:确定对象被使用前已先被初始化

    Effective C++

    Chapter 1. 让自己习惯C++ (Accustoming Yourself to C++)

         Item 4. 确定对象被使用前已先被初始化

                     (Make sure that objects are initialized before they're used.)

     

    通常如果你使用 C part of C++ 而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入 non-C part of C++, 规则有些变化。这就很好地解释了为什么 array (来自 C part of C++)不保证其内容被初始化,而 vector (来自 non-C part of C++)却有次保证。表面上这似乎是一个无法决定的状态,而最佳的处理办法就是:永远在使用对象之前先将它初始化

    1. 对于无任何成员的内置类型,必须手工完成此事例如:

    int x = 0;                                 // 对 int 进行手工初始化
    const char* text = "A C-style string";     //对指针进行手工初始化
    double d;
    std::cin >> d;                             //以读取 input stream 的方式完成初始化

    2. 对于内置类型以外的任何其他东西,初始化责任落在构造函数(constructors)身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。这个规则很容易奉行,重要的是别混淆了赋值(assignment)和初始化(initialization)。考虑一个用来表现通讯簿的 class,其构造函数如下:

    class PhoneNumber { ... };
    class ABEntry       //ABEntry = "Address Book Entry"
    {
    public:
        ABEntry (const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones);
    
    private:
        std::string theName;
        std::string theAddress;
        std::list<PhoneNumber> thePhones;
        int numTimesConsulted;
    };
    ABEntry::ABEntry (const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
    {
        theName = name;           //这些都是赋值(assignment),
        theAdress = address;      //而不是初始化(initialization)。
        thePhones = phones;
        numTimesConsulted = 0;
    }

    这会导致 ABEntry 对象带有你期望(你指定)的值,但不是最佳做法。C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。在 ABEntry 构造函数内,theName, theAddress 和 thePhones 都不是被初始化,而是被赋值。初始化的发生时间更早,发生于这些成员的 default 构造函数被自动调用之时(比进入 ABEntry 的构造函数本体的时间更早)。但这对 numTimesConsulted 不为真,因为它属于内置类型,不保证一定在你所看到的那个赋值动作的时间点之前获得初值。

    ABEntry 构造函数的一个较佳的写法是,使用所谓的 member initialization list (成员初值列)替换赋值动作

    ABEntry::ABEntry (const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
     : theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0)   //现在,这些都是初始化(initialization)
    {
        //现在,构造函数本体不必有任何动作
    }

    这个构造函数和上一个的最终结果相同,但通常效率较高。基于赋值的那个版本首先调用 default 构造函数为 theName, theAddress 和 thePhones 设初值,然后立刻再对它们赋予新值,default 构造函数的一切作为因此浪费了。成员初值列(member initialization list)的做法避免了这一问题,因为初值列中针对各个成员变量而设的实参,被拿去作为各成员变量之构造函数的实参。本例中的 theName 以 name 为初值进行 copy 构造,theAddress 以 address 为初值进行 copy 构造,thePhones 以 phones 为初值进行 copy 构造。

    对于大多数类型而言,比起先调用 default 构造函数然后再调用 copy assignment 操作符,单只调用一次 copy 构造函数是比较高效的,有时甚至高效的多。对于内置型对象如 numTimesConsulted,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。同样道理,甚至当你想要 default 构造一个成员变量,都可以使用成员初值列,只要指定无物作为初始化实参即可。假设 ABEntry 有一个无参数构造函数,可将它实现如下:

    ABEntry::ABEntry( ) : theName(), theAddress(), thePhones(), numTimesConsulted(0)
    {
        //调用 theName, theAddress, thePhones 的default 构造函数
        //记得将 numTimesConsulted 显示初始化为 0 
    }

    记住总是在初值列中列出所有的成员变量,以免还得记住哪些成员变量(如果它们在初值列中被遗漏的话)可以无需初值。

    有些情况下即使面对的成员变量属于内置类型(那么其初始化与赋值的成本相同),也一定得使用初值列。如果成员变量是 const 或 references,它们就一定需要初值,不能被赋值(见 Item 5)。为避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:总是使用成员初值列

    许多 classes 拥有多个构造函数,每个构造函数有自己的成员初值列。如果这种 classes 存在许多成员变量和/或 base classes,多份成员初值列的存在就会导致不受欢迎的重复(在初值列内)和无聊的工作(对程序员而言)。这种情况下可以合理地在初值列中遗漏那些“赋值表现像初始化一样好”的成员变量,改用它们的赋值操作,并将那些赋值操作移往某个函数(通常为 private),供所有构造函数调用。这种做法在“成员变量的初值由文件或数据库读入”时特别有用。然而,比起经常由赋值操作完成的“伪初始化”(pseudo-initialization),通过成员初值列完成的“真正初始化”通常更加可取。

    C++有着十分固定的“成员初始化次序”。是的,次序总是相同:base classes 更早于其 derived classes 被初始化(见 Item 12),而 class 的成员变量总是以其声明次序被初始化。回头看看 ABEntry,其 theName 成员永远最先被初始化,然后是 theAddress, 再来是 thePhones,最后是 numTimesConsulted。即使它们在成员初值列中以不同的次序出现(很不幸那是合法的),也不会有任何影响。因而在成员初值列中列各个成员时,最好以其声明次序为次序。<通俗来讲,两个成员变量的初始化带有次序性,例如初始化 array 时需要指定大小,因此代表大小的那个成员变量必须先有初值。>

    一旦已经很小心地将“内置型成员变量”明确地加以初始化,而且也确保构造函数运用“成员初值列”初始化 base classes 和成员变量,那就只剩唯一一件事情需要操心,就是“不同编译单元内定义的 non-local static 对象”的初值化次序。

    具体略。

    请记住:

    • 为内置型对象进行手工初始化,因为 C++ 不保证初始化它们。
    • 构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。
    • 为免除“跨编译单元的初始化次序”问题,请以 local static 对象替换 non-local static 对象。
  • 相关阅读:
    LeetCode 515. 在每个树行中找最大值(Find Largest Value in Each Tree Row)
    LeetCode 114. 二叉树展开为链表(Flatten Binary Tree to Linked List)
    LeetCode 199. 二叉树的右视图(Binary Tree Right Side View)
    LeetCode 1022. 从根到叶的二进制数之和(Sum of Root To Leaf Binary Numbers)
    LeetCode 897. 递增顺序查找树(Increasing Order Search Tree)
    LeetCode 617. 合并二叉树(Merge Two Binary Trees)
    LeetCode 206. 反转链表(Reverse Linked List) 16
    LeetCode 104. 二叉树的最大深度(Maximum Depth of Binary Tree)
    LeetCode 110. 平衡二叉树(Balanced Binary Tree) 15
    LeetCode 108. 将有序数组转换为二叉搜索树(Convert Sorted Array to Binary Search Tree) 14
  • 原文地址:https://www.cnblogs.com/VVingerfly/p/4542312.html
Copyright © 2011-2022 走看看