不初始化 C++ 构造函数中的每个成员是一种好习惯吗?
Is it good practice to not initialize every member in a C++ constructor?
我有一个 class 有两个状态,不同的成员只适用于一个状态或另一个状态。
哪种做法更好:
- 选项 1:构造函数仅初始化与第一个(初始)状态相关的成员
- 选项 2:初始化每个成员,即使这意味着为成员发明 "uninitialized" 值?
例如
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" 值。更好的做法是在构建对象所需的所有信息都可用之前避免创建对象。
我有一个 class 有两个状态,不同的成员只适用于一个状态或另一个状态。
哪种做法更好:
- 选项 1:构造函数仅初始化与第一个(初始)状态相关的成员
- 选项 2:初始化每个成员,即使这意味着为成员发明 "uninitialized" 值?
例如
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" 值。更好的做法是在构建对象所需的所有信息都可用之前避免创建对象。