问题
I have a struct with many members of the same type, like this
struct VariablePointers {
VariablePtr active;
VariablePtr wasactive;
VariablePtr filename;
};
The problem is that if I forget to initialize one of the struct members (e.g. wasactive
), like this:
VariablePointers{activePtr, filename}
The compiler will not complain about it, but I will have one object that is partially initialized. How can I prevent this kind of error? I could add a constructor, but it would duplicate the list of variable twice, so I have to type all of this thrice!
Please also add C++11 answers, if there's a solution for C++11 (currently I'm restricted to that version). More recent language standards are welcome too, though!
回答1:
Here is a trick which triggers a linker error if a required initializer is missing:
struct init_required_t {
template <class T>
operator T() const; // Left undefined
} static const init_required;
Usage:
struct Foo {
int bar = init_required;
};
int main() {
Foo f;
}
Outcome:
/tmp/ccxwN7Pn.o: In function `Foo::Foo()':
prog.cc:(.text._ZN3FooC2Ev[_ZN3FooC5Ev]+0x12): undefined reference to `init_required_t::operator int<int>() const'
collect2: error: ld returned 1 exit status
Caveats:
- Prior to C++14, this prevents
Foo
from being an aggregate at all. - This technically relies on undefined behaviour (ODR violation), but should work on any sane platform.
回答2:
For clang and gcc you can compile with -Werror=missing-field-initializers
that turns the warning on missing field initializers to an error. godbolt
Edit: For MSVC, there seems to be no warning emitted even at level /Wall
, so I don't think it is possible to warn on missing initializers with this compiler. godbolt
回答3:
Not an elegant and handy solution, I suppose... but should works also with C++11 and give a compile-time (not link-time) error.
The idea is to add in your struct an additional member, in the last position, of a type without default initialization (and that cannot initialize with a value of type VariablePtr
(or whatever is the type of preceding values)
By example
struct bar
{
bar () = delete;
template <typename T>
bar (T const &) = delete;
bar (int)
{ }
};
struct foo
{
char a;
char b;
char c;
bar sentinel;
};
This way you're forced to add all elements in your aggregate initialization list, included the value to explicit initialize the last value (an integer for sentinel
, in the example) or you get a "call to deleted constructor of 'bar'" error.
So
foo f1 {'a', 'b', 'c', 1};
compile and
foo f2 {'a', 'b'}; // ERROR
doesn't.
Unfortunately also
foo f3 {'a', 'b', 'c'}; // ERROR
doesn't compile.
-- EDIT --
As pointed by MSalters (thanks) there is a defect (another defect) in my original example: a bar
value could be initialized with a char
value (that is convertible to int
), so works the following initialization
foo f4 {'a', 'b', 'c', 'd'};
and this can be highly confusing.
To avoid this problem, I've added the following deleted template constructor
template <typename T>
bar (T const &) = delete;
so the preceding f4
declaration gives a compilation error because the d
value is intercepted by the template constructor that is deleted
回答4:
For CppCoreCheck there's a rule for checking exactly that, if all members have been initialized and that can be turned from warning into an error - that is usually program-wide of course.
Update:
The rule you want to check is part of typesafety Type.6
:
Type.6: Always initialize a member variable: always initialize, possibly using default constructors or default member initializers.
回答5:
The simplest way is not to give the type of the members a no-arg constructor:
struct B
{
B(int x) {}
};
struct A
{
B a;
B b;
B c;
};
int main() {
// A a1{ 1, 2 }; // will not compile
A a1{ 1, 2, 3 }; // will compile
Another option: If your members are const & , you have to initialize all of them:
struct A { const int& x; const int& y; const int& z; };
int main() {
//A a1{ 1,2 }; // will not compile
A a2{ 1,2, 3 }; // compiles OK
If you can live with one dummy const & member, you can combine that with @max66's idea of a sentinel.
struct end_of_init_list {};
struct A {
int x;
int y;
int z;
const end_of_init_list& dummy;
};
int main() {
//A a1{ 1,2 }; // will not compile
//A a2{ 1,2, 3 }; // will not compile
A a3{ 1,2, 3,end_of_init_list() }; // will compile
From cppreference https://en.cppreference.com/w/cpp/language/aggregate_initialization
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized. If a member of a reference type is one of these remaining members, the program is ill-formed.
Another option is to take max66's sentinel idea and add some syntactic sugar for readability
struct init_list_guard
{
struct ender {
} static const end;
init_list_guard() = delete;
init_list_guard(ender e){ }
};
struct A
{
char a;
char b;
char c;
init_list_guard guard;
};
int main() {
// A a1{ 1, 2 }; // will not compile
// A a2{ 1, init_list_guard::end }; // will not compile
A a3{ 1,2,3,init_list_guard::end }; // compiles OK
来源:https://stackoverflow.com/questions/60150749/is-it-possible-to-prevent-omission-of-aggregate-initialization-members