什么时候必须使用初始化列表来初始化 C++ 类成员?
-
06-07-2019 - |
题
假设我有 std::map< std::string, std::string > m_someMap
作为A类的私有成员变量
两个问题:(我问的唯一原因是因为我遇到过这样的代码)
此行的目的是什么:
A::A() : m_someMap()
现在我知道这是初始化,但是你必须这样做吗?我很困惑。
默认值是多少
std::map< std::string, std::string > m_someMap
, ,C# 还定义了 int、double 等。总是初始化为Defualt 0,并且对象为null(至少在大多数情况下),那么C ++的规则是什么?对象是否默认初始化为 null,原语初始化为垃圾?当然,我正在考虑实例变量。
编辑:
另外,既然大多数人指出这是一种风格选择,没有必要,那么:
答::A() :m_someMap()、m_someint(0)、m_somebool(假)
解决方案
<强> m_somemap 强>
- 你不必。
- 如果省略它会得到什么:一个空的
std::map< std::string, std::string >
,即该地图中没有元素的有效实例。
醇>
- 如果您希望它具有已知值,则必须将其初始化为
true
或false
。布尔值是<!>“普通旧数据类型<!>”;他们没有构造函数的概念。此外,C ++语言没有为非显式初始化的布尔值指定默认值。 - 如果省略它会得到什么:具有未指定值的布尔成员。您不能这样做,以后再使用它的值。因此,强烈建议您初始化此类型的所有值。 醇>
- 如果您希望它具有已知值,则必须将其初始化为某个整数值。整数是<!> quot;普通旧数据类型<!> quot;他们没有构造函数的概念。此外,C ++语言没有为非显式初始化的整数指定默认值。
- 如果省略它会得到什么:具有未指定值的int成员。您不能这样做,以后再使用它的值。因此,强烈建议您初始化此类型的所有值。 醇>
<强> m_somebool 强>
<强> m_someint 强>
其他提示
没有必要真正去做。
默认构造函数将自动执行此操作。
但有时通过明确表示它可以作为一种文档:
class X
{
std::map<string,string> data;
Y somePropertyOfdata;
X()
:data() // Technically not needed
,somePropertyOfdata(data) // But it documents that data is finished construction
{} // before it is used here.
};
C ++中的规则是,除非你明确地初始化POD数据,否则它是未定义的,而其他类有自动调用的默认构造函数(即使程序员没有明确地这样做)。
但是这样说。考虑一下:
template<typename T>
class Z
{
T data;
Z()
:data() // Technicall not need as default constructor will
// always be called for classes.
// But doing this will initialize POD data correctly
// if T is a basic POD type.
{}
};
在这里,您要将数据默认初始化。
技术上POD没有构造函数所以如果T是int那么你会期望它做什么吗?因为它是显式初始化它被设置为0或POD类型的等价物。
编辑:
class A
{
std::map<string,string> m_someMap;
int m_someint;
bool m_somebool;
public:
A::A()
: m_someMap() // Class will always be initialised (so optional)
, m_someint(0) // without this POD will be undefined
, m_somebool(false)// without this POD will be undefined
{}
};
正如其他人指出的:这不是必需的,但或多或少是风格问题。好处:它表明您明确想要使用默认构造函数并使您的代码更加冗长。不足之处:如果您有多个 ctor,维护所有这些 ctor 的更改可能会很痛苦,有时您添加类成员并忘记将它们添加到 ctor 初始值设定项列表中,从而使其看起来不一致。
A::A() : m_someMap()
在这种情况下,这条线是不必要的。然而, 一般来说, ,这是初始化类成员的唯一正确方法。
如果您有这样的构造函数:
X() : y(z) {
w = 42;
}
那么当以下情况发生时 X
构造函数被调用:
- 首先,初始化所有成员:为了
y
, ,我们明确表示我们希望调用带有z
作为其论点。为了w
, ,会发生什么取决于类型w
. 。如果w
是 POD 类型(即基本上是 C 兼容类型:没有继承,没有构造函数或析构函数,所有成员都是 public,所有成员也是 POD 类型),那么就是 不是 已初始化。它的初始值是在该内存地址发现的任何垃圾。如果w
是非 POD 类型,则调用其默认构造函数(非 POD 类型为 总是 在构造时初始化)。 - 一旦两个成员都构建完成,我们 然后 调用赋值运算符将 42 赋值给
w
.
需要注意的重要一点是所有构造函数都被调用 前 我们进入构造函数的主体。一旦我们进入主体,所有成员都已经被初始化。所以我们的构造函数体可能存在两个问题。
- 如果什么
w
是没有默认构造函数的类型?那么这将无法编译。然后它 必须 在之后显式初始化:
, , 喜欢y
是。 - 如果这个调用顺序会怎样 两个都 默认构造函数和赋值运算符不必要地慢?也许一开始就简单地调用正确的构造函数会更有效。
简而言之,自从 m_someMap
是非POD类型,严格来说我们不需要做 : m_someMap()
. 。无论如何,它都会被默认构建。但如果它是 POD 类型,或者如果我们想调用默认构造函数之外的另一个构造函数,那么我们就需要这样做。
只是要明确发生了什么(关于你的第二个问题)
std::map< std::string, std::string > m_someMap
创建一个名为m_someMap的堆栈变量,并在其上调用默认构造函数。所有对象的C ++规则都是:
T varName;
其中T是一个类型,varName是默认构造的。
T* varName;
应该在新标准中明确赋值为NULL(或0) - 或nullptr。
澄清默认值问题:
C ++没有某些类型隐含的概念被引用。除非将某些内容显式声明为指针,否则不能采用空值。这意味着每个类都有一个默认构造函数,用于在未指定构造函数参数时构建初始值。如果没有声明默认构造函数,编译器将为您生成一个。此外,每当一个类包含属于类别的成员时,这些成员将在对象构造时通过它们自己的默认构造函数隐式初始化,除非使用冒号语法显式调用另一个构造函数。
恰好所有STL容器类型的默认构造函数都构建了一个空容器。其他类可能有其默认构造函数的其他约定,因此您仍然需要知道它们是在这样的情况下调用的。这就是A::A() : m_someMap()
行的原因,它实际上只是告诉编译器做它本来会做的事情。
在C ++中创建对象时,构造函数将按以下顺序进行:
- 调用整个类树中的所有父虚拟类的构造函数(以任意顺序)
- 按声明的顺序调用所有直接继承的父类的构造函数
- 按声明的顺序调用所有成员变量的构造函数 醇>
还有一些比这更具体的细节,有些编译器允许你强制执行这个特定顺序的一些事情,但这是一般的想法。对于每个构造函数调用,您都可以指定构造函数参数,在这种情况下,C ++将按指定的方式调用构造函数,或者您可以不管它,C ++将尝试调用默认构造函数。默认构造函数就是不带参数的构造函数。
如果您的任何虚拟父类,非虚拟父类或成员变量没有默认构造函数或需要使用非默认构造函数创建,则将它们添加到构造函数调用列表中。因为C ++假设一个默认的构造函数调用,所以在列表中放置一个默认构造函数并完全不让它完全没有区别(C ++不会(除非在这个问题范围之外的特殊情况下)创建一个对象而不调用一个某种构造函数)。如果某个类没有默认构造函数而您没有提供构造函数调用,则编译器将抛出错误。
当涉及内置类型float
或int
时,默认构造函数根本不执行任何操作,因此该变量将具有内存中剩余的任何内容的默认值。所有内置类型都有一个复制构造函数,因此您可以通过将其初始值作为变量构造函数的唯一参数传递来初始化它们。