[C++]OOP编程基本准则

让自己习惯C++

1. 视c++为一个语言联邦

C++是一个由相关语言组成的联邦而非单一语言。其中包括:

  • C。内置数据结构等…
  • Object-Oriented C++.包括class, encapsulation(封装), inheritance, polymorphism, vitual function….面向对象的古典守则。
  • Template C++. The important part of generic programming.
  • STL。

记住:C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。

2. 尽量以const,enum,inline替换#define

#define MAX 9999

记号名称MAX可能没==进入记号表==,于是当你运用此常量但获得编译错误信息时,可能呢过会因为看到9999而不是MAX而无法准确追踪错误。而解决之道是以一个常量替换上述的宏。

const int Max = 9999;

作为一个语言常量,Max一定会被编译器看到,并进入记号表。

此外还需要注意两个特别情况:

    1. 定义常量指针。由于常量定义式通常被放在头文件中,因此有必要将指针声明为const。
    1. 关于class专属常量。为了将常量的作用域限制于class内,并且确保常量至多只有一份实体,必须将他声明为static成员。

static const int numTurns = 5;

这是一个声明式而非定义式。通常C++要求你对你使用的任何东西提供一个定义式,但如果它是class的专属常量又是static整型,则需特别处理。只要不取他们的地址,你可以声明并使用他们而无需提供定义式。

const int Namespace::numTurns;

这是一个定义式,应该把它放在实现问题中,由于声明时已经获得初值,定义时不再设初值。

The enum hack 补偿方法:一个属于枚举类型的数值可以权充int被使用。并且不能被取地址。

记住:

对于单纯常量,最好以consy对象或enums替换#defines。

对于形似函数的宏,最好改用inline函数替换#defines。

3. 尽可能使用const

如果const出现在星号左边,表示被指物是常量,如果出现在星号右边,表示指针自身是常量,如果出现在两边则都是。

const成员函数

将const实施于成员函数的目的,是为了确认该成员函数可作用域const对象身上。这一类成员函数之所以重要,基于以下两个理由:

  • 他们使class接口更容易理解。
  • 他们使操作const对象成为可能。改善c++程序效率的一个根本办法就是以pass-by reference-to-const方式传递对象,而此技术可行的前提是,我们有const成员函数来处理取得的const对象。
class TextBlock {
public:
    ...
    const char& operation [] (std::size_t position) const {
    return text[position];
    }  // operator [] for const object.
    char& operation [] (std::size_t position) {
    return text[position];
    }   // operator [] for non-const object.

把一个成员函数定义为const,意味着对他返回值不能操作,但只要返回一个by value就是实现这个功能,那么成员函数是congst究竟意味什么?

这里有两个流行概念:

bitwise constness

此阵营的人认为,成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const,也就是说不能更改对象内热任何一个bit,也就是说它不更改对象内任何一个bit!(编译器只要检查赋值行为即可)

然而,许多成员函数虽然不十分具备const性质却能通过bitwise测试。更具体地说,一个更改了“指针所指物”的成员函数虽然不能算是const,但如果只有指针(而非所指指物)隶属于对象,那么此函数为bitwise const不会引发编译器异议。

class CText {
public:
    ...
    char& operator [] (std::size_t position) const {
        return text[position];
    }  
    // 不适当,把函数声明为const却返回一个reference纸箱对象内部值。
private:
    char* text;
}

const CText cctb("Hello");
char* pc = &cctb[0];
*pt = "J";  // successive! cctb : "Jello" now.

此情况导出所谓logical constness。

logical constness

一个const成员可以修改它所处理的对象内的某些bit,但只有在客户端侦测不出来时才可以。

利用mutable就可以释放掉non-static成员变量的bitwise constness约束。

在const和non-const成员函数中避免重复

class text {
public:
    const char& operator [] (std::size_t position) const {
        return text[position];
    }
    char& operator[] (std::size_t position) {
    return const_cast<char&>(
        static_cast<const Text&>(*this)[position]);
    {

值得注意的是,方向操作–令const版本调用non-const版本以避免错误,是不该做的。因为,const成员函数承诺绝不改变其对象的而逻辑状态,non-const成员函数却没有这般承诺。如果在const函数内调用non-const函数,对象可能会被改变。

请记住:

  • const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体,有助于帮助编译器侦测出错误。
  • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”。
  • 用non-const版本调用const版本可以避免代码重复。

确定对象被使用前已被初始化

构造函数

在构造函数中最好使用初始化列表,因为在初始化列表中才是真正的初始化,效率更高。另外在初始化列表中还要注意次序的问题,一般来说是按照成员变量的声明次序来初始化变量的。

不同编译单元内定义之non-local static 对象

所谓static对象,其寿命从被构造出来知道程序结束为止,因此stack和heap-based对象都被删除。在函数内的的static对象称为local-static对象(因为他们对函数而言是local),其他static对象称为non-local static对象。程序结束时static对象会被自动销毁,也就是他们的析构函数会在main()结束时被自动调用。

所谓编译单元是指产出单一目标文件的那些源码。基本上它是单一源码文件加上其所含入的头文件。

现在,我们关系的问题是,对于两个以上源码文件,每一都含有至少一个non-local static对象。真正的问题是:如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义不同编译单元的non-local static对象”的初始化次序没有明确定义。

实例可以帮助理解:

class FileSystem {
public:
    ...
    std::size_t numDisk() const;
    ..
};
extern static FileSystem tfs;
class Directory {
public:
    Directory(params) {
        ...
        std::size_t disks = tfs.numDisks();
        // error occurs!
    ...
    }
};

问题发生了!你不知道disk在调用tfs之前,tfs有没有被初始化!!!

C++对“定义域不同编译单元内的non-local static对象”的初始化次序没有明确定义!因为这是没有必要的。

幸运的是,我们可以使用Singleton模式来解决这个问题。这个手法的基础在于:C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇到该对象之定义式”时被初始化。所以如果你以“函数调用”(返回一个reference指向local static对象)替换“直接访问non-local static对象”,你就可以保证reference指向一个历经初始化的对象。

class FileSystem {...}; // just the same as before.
FileSystem& tfs() {
    static FileSystem fs;
    return fs;
}
class Directory {
public:
    Directory(params) {
        ...
        std::size_t disks = tfs().numDisks();
        // error occurs!
    ...
    }
};
Directory& tempDir() { // 这个函数用来替换tempDir对象。
    static Directory td;
    return td;
}

请记住:

  • 为内置型对象进行手工初始化,因为C++不保证初始化他们。
  • 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。
  • 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

你可能感兴趣的:([C++]OOP编程基本准则)