不初始化 C++ 构造函数中的每个成员是一种好习惯吗?

Is it good practice to not initialize every member in a C++ constructor?

我有一个 class 有两个状态,不同的成员只适用于一个状态或另一个状态。

哪种做法更好:

例如

class Foo {
public:
  enum State { A, B };

  // Constructor
  // Option 1: Only initialize first state
  Foo(int a1) : _state(A), _a1(a1) {}
  // ... or ... 
  // Option 2: initialize every member
  Foo(int a1) : _state(A), _a1(a1), b1(-1), b2(-1) {}

  State getState() const { return _state; }

  // Only applicable if State is A
  int getA1() const { return _a1; } // Might also add assert that State is A

  // Only applicable if State is B
  int getB1() const { return _b1; } // Might also add assert that State is B
  int getB2() const { return _b2; } // Might also add assert that State is B

private:
  State _state;

  int _a1;

  int _b1;
  int _b2;
};

我认为初始化所有内容是一种很好的做法。

对于一个状态,给这些值一个不应设置的无效值

PS: 对于初始化列表,按照声明的顺序进行。

读取未初始化的变量是未定义的行为,因此如果您使用选项 1 然后有人调用 getB1()getB2(),您就有未定义的行为。

选项 1 本质上没有错,只要您清楚地记录调用这些 getter 可能会调用未定义的行为以及在什么情况下会发生这种情况。通过这种方式,您可以将确保定义行为的负担转移给此 class.

的消费者。

您还可以存储指示它们是否已被初始化的标志,如果在它们被初始化之前尝试读取则抛出异常。这样你会得到一个明确定义的错误而不是 UB。 (您也可以在这里使用 boost::optional<int>,它负责为您提供这个额外的标志。)

考虑到所有这些要点,使用 "dummy" 值可能更可取,因为不存在未定义行为的风险并且实现更简单。 (如果你确实使用了一个虚拟值,请确保你提供了一个静态常量值,以便调用者可以比较以查看该值是否尚未设置。)

这可能是使用联合体作为成员的情况。您可以使用联合结构来节省内存 space,同时让断言检查要使用的联合成员的状态。

struct BMembers {
  int _b1;
  int _b2;

};

union MyUnion {
  int _a1;
  BMembers members;
};

class Foo {
public:
  enum State { A, B };

  Foo(int a1) : _state(A), myUnion._a1(a1) {}

  State getState() const { return _state; }

  // Only applicable if State is A
  int getA1() const { return myUnion._a1; } // Might also add assert that State is A

  // Only applicable if State is B
  int getB1() const { return myUnion._b1; } // Might also add assert that State is B
  int getB2() const { return myUnion._b2; } // Might also add assert that State is B

private:
  State _state;

  MyUnion myUnion;
};

这取决于您的具体情况。在声明时初始化每个变量是一个好习惯。 在一个大程序中,我们可能会忘记初始化变量,这可能会导致代码转储。 稍后我们将需要付出额外的努力来解决这些初始化问题。 因此,在声明本身时初始化变量是一种很好的编码习惯。 为此,我们使用 类.

的构造函数

在这种特殊情况下,第二种选择更好。如果您调用 getB1() 或 getB2(),会发生什么情况,它们 return 垃圾值。

如果他妈的确定它不会被调用,那没关系。但最好初始化它们。

初始化所有内容是一个好习惯。也许您的对象没有真正的价值可以算作 "uninitialized" 并且会破坏您的代码。 作为解决方案,请考虑使用单独的 类 来定义每个状态。这样您就可以更好地记录每个州所需的内容,并且根据成员的规模,可以通过仅存储您需要的内容来节省 space:

class Foo{
public:
  enum State{A,B};
  virtual State getState() const = 0;
  virtual int getA1() const = 0;
  virtual int getB1() const = 0;
  virtual int getB2() const = 0;
};

class Foo_A : public Foo{
public:
  Foo_A(int a1) : _a1(a1) {} // State implicit
  State getState() const {return A;}
  int getA1() const {return _a1;}
  int getB1() const {throw "Bad Call";} // For simplicity. You should use a class derived from std::exception;
  int getB2() const {throw "Bad Call";}
private:
  int _a1;
};

class Foo_B : public Foo{
public:
  Foo_B(int b1, int b2) : _b1(b1), _b2(b2) {}
  State getState() const {return B;}
  int getA1() const {throw "Bad Call";}
  int getB1() const {return _b1;}
  int getB2() const {return _b2;}
private:
  int _b1;
  int _b2;
};

让任何成员未初始化是任何具有足够访问权限的函数(成员、朋友等)在初始化之前尝试访问该成员的绝佳机会。这会导致未定义的行为——通常在构造函数之外的代码中。由于问题的根本原因(在构造函数中未初始化)和触发器(访问)在代码中的不同位置,这意味着错误难以识别,因此难以纠正。

因此,一般来说,最好在构造函数中初始化所有成员。

但是,这并不意味着某些成员的 "inventing" 值。更好的做法是在构建对象所需的所有信息都可用之前避免创建对象。