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

时间:2015-01-26 18:53:28

标签: c++ oop

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

哪种方式更好:

  • 选项1:构造函数仅初始化与第一个(初始)状态相关的成员
  • 选项2:初始化每个成员,即使这意味着为成员发明“未初始化”值?

E.g。

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;
};

6 个答案:

答案 0 :(得分:3)

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

选项1 没有任何内在错误,只要您清楚地记录调用这些getter可能会调用未定义的行为以及可能发生的情况。这样,您就可以将确定已定义行为的负担转移到此类的使用者身上。

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

考虑所有这些要点,使用&#34;虚拟&#34;值可能是首选,因为不存在未定义行为的风险并导致更简单的实现。 (如果您使用虚拟值,请确保提供静态常量值,以便调用者可以进行比较以查看该值是否已设置。)

答案 1 :(得分:2)

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

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;
};

答案 2 :(得分:1)

初始化所有内容是一种很好的做法。也许你的对象没有真正的价值,可以算作“未初始化”,这会破坏你的代码。 作为解决方案,请考虑使用单独的类来定义每个状态。这样,您可以更好地记录每个州所需的内容,并且,根据您的成员的大小,可以通过仅存储您需要的内容来节省空间:

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;
};

答案 3 :(得分:0)

我希望将一切都投入使用是好的做法。

对于某个州,请将这些值设为无效的值

PS:对于初始化列表,按照与声明相同的顺序执行它们。

答案 4 :(得分:0)

这取决于您的具体情况。在声明中初始化每个变量是一种很好的做法。 在一个大程序中,我们可能忘记初始化变量,这可能导致代码转储。 稍后我们将需要额外的努力来找出这些初始化问题。 因此,在声明本身初始化变量是一种很好的编码实践。 为此,我们对类使用构造函数。

在这种特殊情况下,第二种选择更好。如果你调用getB1()或getB2(),它们会返回垃圾值。

如果那是确定它不会被召唤,那就可以了。但它更好地初始化它们。

答案 5 :(得分:0)

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

因此,一般情况下,最好初始化构造函数中的所有成员。

然而,这并不意味着某些成员“发明”价值。更好的做法是避免创建对象,直到构建它所需的所有信息都可用为止。