我有以下一段代码正在工作,但我不明白为什么它能工作(灵感来自现实生活中的代码库(:
基类定义:
class Pointers {
private:
int* Obj1;
double* Obj2;
public:
Pointers(int* Obj1_, double* Obj2_) : Obj1{Obj1_}, Obj2{Obj2_} {}
};
现在,我们从基类派生一个类,在基类中,我们用同名的int
和double
对两个指针进行阴影处理:
class Objects : public Pointers {
public:
int Obj1{69};
double Obj2{72};
Objects() : Pointers(&Obj1, &Obj2) {}
};
在Objects
的构造函数中,我们调用Pointers
的构造函数,并传递Obj1
和Obj2
的地址。这实际上是有效的:(阴影(指针将指向Obj1 (69)
和Obj2 (72)
。
我的问题是:为什么这样有效?我认为在第一步中构造基类成员(它们是两个指针(,然后才构造派生类成员(具有相同名称的int
和double
(。我们如何首先将这些对象地址传递给基类构造函数?
它是有效的,因为当您调用基类构造函数时,派生类成员
int Obj1{69};
double Obj2{72};
尚未初始化,但它们的地址是已知的,并且您正在使用它们的地址初始化基类中的指针。请注意,它与阴影几乎没有关系。
当然,如果您尝试在Pointers
构造函数中打印指向值
Pointers(int* Obj1_, double* Obj2_) : Obj1{Obj1_}, Obj2{Obj2_} {
std::cout << "Obj1: " << *Obj1 << ", Obj2: " << *Obj2 << std::endl;
}
您将得到一些垃圾(UB(,因为指向的值尚未初始化。为了使代码更加清晰,避免在类主体中分配成员字段,而是使用构造函数成员初始值设定项列表:
Objects() : Pointers(&Obj1, &Obj2), Obj1{69}, Obj2{72} {}
类的初始化实际上如下进行:
- 确定用于构造对象的内存(例如堆栈位置或使用
new
运算符的结果( - 输入
Object::Object()
的调用 Pointers::Pointers(int* Obj1_, double* Obj2_)
被调用这发生在执行Object
成员的任何初始化程序之前内存位置已经可用(请参见步骤1.(,因此可以使用操作员的地址- 对象的
Pointers
部分初始化完成 Object
的成员变量被初始化
简单地注意,可以简单地传递成员变量的地址,但您需要确保指针在基类构造函数中没有被取消引用,因为这会导致未定义的行为。
此处:
class Objects : public Pointers {
public:
int Obj1{69};
double Obj2{72};
Objects() : Pointers(&Obj1, &Obj2) {}
};
CCD_ 18是指CCD_。如果在Pointers
中存在相同的命名成员,则它被Objects
中的成员遮蔽。您需要限定名称Pointers::Obj1
。你不这么做,所以Obj1
指的是Objects::Obj1
。这里没什么奇怪的。
然后在这里:
class Pointers {
private:
int* Obj1;
double* Obj2;
public:
Pointers(int* Obj1_, double* Obj2_) : Obj1{Obj1_}, Obj2{Obj2_} {}
};
没有任何东西被遮蔽,Obj1_
和Obj1
是不同的标识符。基类使用传递给构造函数的指针初始化其成员。
这似乎表明,当指针的ctor在对象的ctor中被调用时,派生类的成员将位于内存中的哪个地址是已知的?
是。在初始化正在构造的对象的成员之前,可以使用这些成员的地址和引用。只要不取消引用指针(或尝试通过引用读取成员的值(,一切都很好。例如,如果在基本构造函数主体中添加一些std::cout << *Obj1;
,则代码将调用未定义的行为,因为Obj
指向的int
此时未初始化。