【问题标题】:Is it good practice to not initialize every member in a C++ constructor?不初始化 C++ 构造函数中的每个成员是一种好习惯吗?
【发布时间】:2015-01-27 06:14:36
【问题描述】:

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

哪种做法更好:

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

例如

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

【问题讨论】:

  • 经验法则:初始化所有成员(C++ 11:您可以编写 int _a1 = 0 进行默认初始化)。但每条规则都适用于豁免。

标签: c++ oop


【解决方案1】:

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

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

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

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

【讨论】:

  • 对于int 成员,这是未定义的行为,还是您只是获得了未指定的值?
  • @crashmstr 未定义行为
  • @crashmstr related
【解决方案2】:

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

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

【讨论】:

  • 不错的示例 - 甚至将所有变量都作为成员。 (当然,至少应该有一个断言)
  • 这是个好主意...它消除了问题,因为不可能同时初始化联合的 A 和 B 部分。
  • 我刚刚发现这叫做“标记联合”:en.wikipedia.org/wiki/Tagged_union
  • 整洁!很高兴知道!谢谢。
【解决方案3】:

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

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

【讨论】:

  • 这是一个不错的主意,但问题是你不能有一个可以处于状态 A 或 B 的对象。如果你想要一个对象,你必须使其成为包含 Foo_A 或 Foo_B 的代理。
  • 是的,没错。仅当您能够承受对性能的影响时才考虑使用它(这应该不是问题,除非您有大量对象并且您需要获得的每一滴性能),并且如果您需要避免 UB。请注意,使用此设置,根本不可能忘记初始化。否则,正如 PDizzle 指出的那样,联合可能是最好的解决方案。
【解决方案4】:

我认为初始化所有内容是个好习惯。

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

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

【讨论】:

    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

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

      因此,通常最好在构造函数中初始化所有成员。

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

      【讨论】:

        猜你喜欢
        • 2019-02-01
        • 1970-01-01
        • 2021-06-19
        • 2021-04-11
        • 1970-01-01
        • 1970-01-01
        • 2017-10-11
        • 1970-01-01
        • 2020-03-13
        相关资源
        最近更新 更多