条款4:确定对象被使用前已先被初始化

简介: 条款4:确定对象被使用前已先被初始化

确定对象被使用前已先被初始化,言简意赅。

因为如果直接使用未初始化的对象,初值未定,行为是不明确的,可能会让程序终止运行,或者发生不可预测的行为等。在c++中,可以通过该类的构造函数初始化该类所包含的所有成员变量。引用书中的说法:“确保每一个构造函数都将对象的每一个成员初始化”

需要注意的是在使用构造函数初始化对象时应当使用构造函数的初始化列表初始化成员,而不要是赋值运算,比如:

class MyType {
public:
    MyType() {
        cout << "default constructor" << endl;
    }
    MyType(const MyType &) {
        cout << "copy constructor" << endl;
    }
    MyType& operator=(const MyType &myType) {
        cout << "operator = " << endl;
        return *this;
    }
};
class Demo {
public:
    Demo(const MyType &myType);
private:
    MyType myType;
};
//第一种初始化myType的方法
Demo::Demo(const MyType &myType) : myType(myType) {
}
//第二种初始化myType的方法
Demo::Demo(const MyType &myType) {
    this->myType = myType;
}
int main()
{
    cout << "myType init begin" << endl;
    MyType myType;
    cout << "myType init end" << endl << endl;
    Demo demo(myType);
}

在Demo类中有一个自定义的MyType类型的成员变量,在构造函数初始化时应当使用第一种初始化的方法,而不是第二种。

分别运行两种初始化的方法可以发现,第一种只调用了拷贝构造函数:

myType init begin
default constructor
myType init end
copy constructor

而第二种首先使用MyType默认构造函数初始化成员变量myType,而后又调用了赋值运算赋初值:

myType init begin
default constructor
myType init end
default constructor
operator =

对于大多数类型而言,比起先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数是比较高效的,有时甚至高效得多。对于内置类型如int,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。

如果成员变量是const或references,它们就一定需要初值,不能被赋值。为避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:总是使用成员初值列。这样做有时候绝对必要,且又往往比赋值更高效。

C++有着十分固定的成员初始化次序。基类更早于派生类被初始化,类的成员变量总是以其声明的次序被初始化。

不论成员初值列是以什么顺序初始化成员变量,成员变量的初始化顺序都只与声明的顺序有关。

C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。

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

函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。

比如,有一个FileSystem class

class FileSystem{
public:
  ...
  std::size_t numDisks() const;
  ...
};
extern FileSystem tfs;

当用户使用该类建立一个处理文件系统的目录类时,很自然的会用到tfs对象:

class Directory{
public:
  Directory(params);
  ...
};
Directory::Directory(params)
{
  ...
  std::size_t disks = tfs.numDisks();
  ...
}

但是,当创建Directory对象tempDir时:

Directory tempDir(params);

如何保证tfs一定会在tempDir之前被初始化?

tfs和tempDir是不同的人在不同的时间于不同的源码文件建立起来的。C++对这种定义于不同编译单元内的non-local static对象的初始化相对次序并无明确定义。

我们可以将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。

C++保证,函数内的local static对象会在“该函数被调用期间” “首次遇上该对象之定义式”时被初始化。所以以“函数调用“(返回一个reference 指向local static对象)替换”直接访问non-local static对象“就获得了一个保证,保证所得的那个reference将指向一个历经初始化的对象。

就像这样:

class FileSystem {...};
FileSystem& tfs()
{
  static FileSystem fs;
  return fs;
}
class Directory {...};
Directory::Directory(params)
{
  ...
  std::size_t disks = tfs().numDisks();
  ...
}
Directory& tempDir()
{
  static Directory td;
  return td;
}

这么修改后,这个系统程序的客户完全像以前一样使用它,唯一不同的是他们现在使用tfs()和tempDir()而不是tfs和tempDir。也就是说他们使用函数返回的”指向static对象“的references,而不再使用static对象自身。这样就可以防止使用跨编译单元的non-local static对象时,对象未初始化的问题。

相关文章
|
13天前
|
设计模式
在静态方法中访问类的实例属性和方法时会发生什么?
总之,静态方法主要用于处理与类本身相关的操作和逻辑,不应该直接访问类的实例属性和方法。如果需要在静态方法中使用与实例相关的信息,应该通过合理的参数传递或其他设计模式来实现,以保持代码的清晰性和面向对象设计的原则。
40 8
|
7天前
|
搜索推荐
如何在构造函数中为类的实例添加方法?
在实际开发中,要根据具体的场景和需求来选择合适的方式来为类的实例添加方法,以确保代码的可读性、可维护性和性能。
|
1月前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
52 4
|
2月前
|
设计模式 前端开发 安全
Qt注册类对象单例与单类型区别
在进行开发时,应当根据具体的应用场景和需求来选择使用单例模式或是单类型。如果是全局服务或状态管理,可能需要单例模式;如果是为了使QML环境下的不同组件能够访问到同一个后端服务对象,则可能需要使用单类型。
40 2
|
5月前
|
C++
友元是一种允许某些外部函数或类访问另一个类的成员的机制
友元是C++中的一个强大特性,它提供了一种机制来允许特定的外部函数或类访问私有和保护成员。正确使用友元可以增强程序的功能性和灵活性,但应谨慎使用以避免破坏封装性和增加代码的复杂度。在设计类和功能时,合理利用友元,可以使得C++程序更加有效和高效。
49 2
|
6月前
|
安全 编译器 C++
C++类与对象【对象的初始化和清理】
C++类与对象【对象的初始化和清理】
C++类与对象【对象的初始化和清理】
|
6月前
|
前端开发 算法 JavaScript
检查是否是类的对象实例
检查是否是类的对象实例
50 0
|
设计模式
单子设计模式 (对创建初始对象为静态,构造函数私有,返回值为对象的创建函数,private应用)
单子设计模式 (对创建初始对象为静态,构造函数私有,返回值为对象的创建函数,private应用)
单子设计模式 (对创建初始对象为静态,构造函数私有,返回值为对象的创建函数,private应用)
|
JavaScript 前端开发
证明函数是对象的特殊子类型和函数的对象属性
证明函数是对象的特殊子类型和函数的对象属性