C++在类构造函数中定义了一个常量成员变量

C++ defining a constant member variable inside class constructor

本文关键字:一个 常量 变量 成员 构造函数 定义 C++      更新时间:2023-10-16

通常,当你的类中有一个常量私有成员变量,它只有一个getter而没有setter时,它看起来像这样:

// Example.h
class Example {
public:
Example(const int value);
const int getValue() const;
private:
const int m_value;
};

// Example.cpp
#include "Example.h"
Example::Example(const int value)
: m_value(value)
{
}
const int Example::getValue() const
{
return m_value;
}

现在我要做的是,有一个像这样的常量int成员变量,但不是在初始化部分这样定义它:: m_value(value),我需要取另一个对象(在本例中我将使用向量)作为构造函数的参数,并根据参数对象设置m_value。在这种情况下,如果向量的大小大于0,我将尝试将其大小设置为+1。这就是我所做的:

Example::Example(std::vector<Example*> myVec)
{
if (myVec.size()) {
m_value = myVec.size() + 1;
}
else {
m_value = -1;
}
}

但我得到了一个错误uninitialized member 'Example::m_value' with 'const' type 'const int',如果我在初始化部分初始化m_value,我就会得到错误assignment of read-only data-member 'Example::m_value',这对我来说很有意义,我应该得到这些错误,但我怎么能绕过它们呢?

编辑:编辑m_value的唯一方法是在对象本身内部(因为m_value是私有的)。只有getter会限制我将m_value设置为构造函数中设置的值以外的任何值。将常量int作为成员变量有什么好处吗?

使用计算所需的静态成员函数,并在初始化列表中调用该函数。像这样:

// Example.h
class Example {
public:
Example(const int value);
Example(std::vector<Example*> myVec);
const int getValue() const;
private:
const int m_value;
static int compute_m_value(::std::vector<Example*> &myVec);
};
// Example.cpp
#include "Example.h"
Example::Example(const int value)
: m_value(value)
{
}
Example::Example(std::vector<Example*> myVec)
: m_value(compute_m_value(myVec))
{
}
const int Example::getValue() const
{
return m_value;
}
int Example::compute_m_value(::std::vector<Example*> &myVec)
{
if (myVec.size()) {
return myVec.size() + 1;
}
else {
return -1;
}
}

在这种特殊情况下,函数非常简单,您可以简单地在构造函数中使用三元运算符(也称为: m_value(myVec.size() > 0 ? int(myVec.size() + 1) : int(-1))来直接计算初始化时的值。这看起来像是一个例子,所以我给了你一个非常通用的解决问题的方法,即使计算你需要的答案的方法可能非常复杂。

一般的问题是常量成员变量(以及作为BTW引用的成员变量)必须在初始值设定项列表中初始化。但是初始化器可以是表达式,这意味着它们可以调用函数。由于这个初始化代码非常特定于类,所以它应该是类的私有函数(或者可能是受保护的函数)。但是,由于调用它是为了在构造类之前创建一个值,因此它不能依赖于类实例的存在,因此没有this指针。这意味着它需要是一个静态成员函数。

现在,myVec.size()的类型是std::vector<Example*>::size_t,并且该类型是无符号的。而您使用的sentinel值为-1,但事实并非如此。你把它存储在一个int中,它的大小可能不适合放它。如果你的矢量很小,这可能不是问题。但是,如果向量的大小是基于外部输入的,或者如果你不知道它会有多大,或者任何其他因素,这将成为一个问题。您应该考虑一下这一点,并相应地调整您的代码。

首先,变量是在类定义中定义的,而不是在构造函数中。它在构造函数中被初始化

其次,实现这一点的方法就像构造函数当前所做的那样:将初始值设定项列表中的值存储在其中:

Example::Example(std::vector<Example*> myVec)
: m_value(myVec.size() ? myVec.size() + 1 : -1) {
}

您有两个基本选项。一种是使用条件运算符,它适用于像您这样的简单条件:

Example::Example(const std::vector<Example*> &myVec)
: m_value( myVec.size() ? myVec.size() + 1 : -1)
{}

对于更复杂的事情,可以将计算委托给成员函数。注意不要在它内部调用虚拟成员函数,因为它将在构造过程中被调用。使其成为static:是最安全的

class Example
{
Example(const std::vector<Example*> &myVec)
: m_value(initialValue(myVec))
{}
static int initialValue(const std::vector<Example*> &myVec)
{
if (myVec.size()) {
return myVec.size() + 1;
} else {
return -1;
}
}
};

当然,后者也适用于类外定义。我把它们放在课堂上是为了节省空间;打字。

这个答案解决了所有其他答案的问题:

这个建议很糟糕:

m_value(myVec.size() ? myVec.size() + 1 : -1)

条件运算符将其第二个和第三个操作数转换为通用类型,而不考虑最终的选择。

在这种情况下,size_tint的常见类型是size_t。因此,如果向量为空,则将值(size_t)-1分配给intm_value,这是一个超出范围的转换,调用实现定义的行为。


为了避免依赖于实现定义的行为,代码可以是:

m_value(myVec.size() ? (int)myVec.size() + 1 : -1)

现在,这保留了原始代码存在的另一个问题:myVec.size() >= INT_MAX时的转换超出范围。在健壮代码中,这个问题也应该得到解决。

我个人更喜欢添加一个helper函数的建议,该函数执行此范围测试,并在值超出范围时抛出异常。虽然代码开始变得难以阅读,但一行代码是可能的:

m_value( (myVec.empty() || myVec.size() >= INT_MAX) ? -1 : (int)myVec.size() + 1 )

当然,还有一些其他方法可以更干净地处理这个问题,例如,将size_t用于m_value,并将(size_t)-1作为哨兵值,或者最好完全避免对哨兵值的需要。