我正在使用此处列出的书籍学习C++。现在,我看到了C++Primer的以下声明:
当我们分配一块内存时,我们通常计划在内存。在这种情况下,我们希望将内存分配与对象解耦建筑
将初始化与分配结合起来通常是我们在分配一个对象。在这种情况下,我们几乎可以肯定地知道对象的值应该有。
(强调矿)
这里需要注意的重要一点是,C++引物似乎表明构造与初始化相同,并且它们不同于分配,这对我来说很有意义
请注意,我只是引用了整个段落中的部分内容,以保持讨论的简洁性并使我的观点得到理解。如果你愿意,你可以在这里阅读完整的段落。
现在,我发现class.dtor中有以下语句:
对于具有非平凡构造函数的对象,在构造函数开始执行之前引用对象的任何非静态成员或基类会导致未定义的行为。对于具有非平凡析构函数的对象,在析构函数完成执行后引用该对象的任何非静态成员或基类会导致未定义的行为。
(强调矿)
现在标准是否准确地指定了构造函数执行的开始时间(在什么时候)
为了给您更多的上下文,请考虑以下示例:
class A {
public:
A(int)
{
}
};
class B : public A {
int j;
public:
int f()
{
return 4;
}
//------v-----------------> #2
B() : A(f()),
//------------^-----------> #3
j(f())
//------------^-----------> #4
{ //<---------------#5
}
};
int main()
{
B b; #1
return 0;
}
我的问题是:
派生类的构造函数
B::B()
在什么时候开始根据标准执行?注意,我知道A(f())
是未定义的行为。B::B()
是否在点#1
、#2
、#3
、#4
或#5
开始执行。换句话说,标准是否准确地指定了构造函数何时(在什么时候)开始执行在这个给定的例子中,构造和初始化是一样的吗。我的意思是,我理解在我们有
j(f())
的成员初始化器列表中,我们正在初始化数据成员j
,但这种初始化是否也意味着构造B::B()
已在点#4
开始执行?
我在最近的SO帖子中读到,派生ctor的执行从点#4
开始,因此该帖子似乎也表明初始化和构造是相同的。
在问这个问题之前,我读了很多帖子,但我没能想出一个符合C++标准的答案。
我还读到了这篇文章,这似乎表明分配、初始化和构建都是不同的:
- 分配
这是为对象分配内存的步骤- 初始化
这是与语言相关的对象属性为"的步骤;设置";。vTable和任何其他";语言实现";完成相关操作- 构造
现在已经分配并初始化了对象,正在执行构造函数。是否使用默认构造函数取决于对象的创建方式
正如您在上面看到的,用户声称所有提到的术语与C++引物建议的术语不同。因此,根据标准、C++Primer(表示构造和初始化相同)或上述引用的答案(表示构造与初始化不同),此处哪个说法是正确的。
初始化和构造有些相似,但不相同。
对于类类型的对象,初始化是通过调用构造函数来完成的(也有例外,例如聚合初始化不使用构造函数;值初始化在调用构造函数之前将成员清零)。
非类类型没有构造函数,因此初始化时没有构造函数。
您的C++初级读本引用使用";初始化";以及";构造";指的是同一件事。将其称为";初始化";不局限于类类型,并包括除构造函数调用之外的初始化的其他部分。
标准是否准确指定构造函数执行的开始时间(在什么时候)?
是。B b;
是默认初始化,对于类,它调用默认构造函数而不执行其他任何操作。
因此,默认构造函数B()
是这里执行的第一件事。
如上所述,B()
在评估其参数f()
之后调用A()
,然后在评估其初始化器f()
之后初始化j
。最后,它执行自己的主体,在您的情况下它是空的。
由于主体是最后执行的,因此认为B()
本身是在A()
之后和/或初始化j
之后执行的是一种常见的误解。
我在最近的SO帖子中读到,派生ctor的执行从点#4 开始
你也应该阅读我对那篇文章的评论,对这句话提出质疑。
示例中的点1和点2没有区别。这是同一点。对象的构造函数在对象实例化时被调用。无论是第1点还是第2点,这都无关紧要。
这里要分离的是对象底层内存的分配(第1点)以及新对象的构造函数何时开始执行(第2点)。
但这是一个没有区别的区别。这不是C++中的两个离散事件,它们可以以某种方式作为离散、独立的步骤来执行。它们是不可分割的,它们是一体的。你不能为C++对象分配内存,但要避免构造它。同样,如果不先为它分配内存,你也不能构造某个对象。这是一样的事情。
现在,您确实有其他可能发生的干扰,比如使用placement-new操作符的服务(以及在以后的某个时刻手动调用对象的析构函数)。这似乎表明分配是分开的,但事实并非如此。placement-new操作符的调用实际上是从中为新对象隐式分配内存。您将指针交给placement-nnew操作符,然后立即进行对象的构造。这就是思考的方式。所以分配+建设仍然是不可分割的一步。
另一个需要理解的重要点是,每个对象的构造函数所做的第一件事就是调用它所派生的所有对象的构造函数。因此,从实际的角度来看,实际上在所示代码中发生的第一件事是,首先调用A
的构造函数来构造它,然后,一旦构造完成,B
的";真实的";进行施工。
B() : A(f()),
这正是上面写的。B
的构造函数开始执行,它的第一个业务顺序是调用A
的构造函数。在A
的构造函数完成之前,B
不执行任何其他操作。因此,从技术上讲,B
的构造函数首先开始执行,然后是A
的构造函数。但是,在A
的构造函数处理其业务之前,B
的构造函数什么也不做。
B
的构造函数所做的第一件事就是调用A
的构造函数。这在C++标准中有规定。
B() : j(f()), A(f())
如果你试图这样做,你的编译器会对你大喊大叫。
因此,总结一下:当你实例化一个对象,任何对象时,首先发生的事情就是调用它的构造函数。就这样,故事结束了。这里的每一种可能的变化(放置新的、没有构造函数的POD、具有虚拟调度的额外歌舞例程)都是这一点的下游,并且可以被定义为构造函数的特殊和特定的实现。