I am seeing some strange behavior which I have described in this post vector of structs inline initialization. braced-init-list
I am hoping to eliminate one possible source of the undefined behavior. I have the following two classes A
and B
which both forward declare struct Foo
but the cpp file defines the actual struct as below.
// This is A.h
namespace app {
struct Foo;
class A {
private:
std::vector<Foo> fooList;
};
}
// This is A.cpp
struct app::Foo {
std::string first;
std::string second;
unsigned flag;
};
// This is B.h
namespace app {
struct Foo;
class B {
private:
std::vector<Foo> fooList;
};
}
// This is B.cpp
struct app::Foo {
std::string first;
std::string second;
bool flag;
float value;
};
Notice, that the struct Foo
has different members in cpp files A.cpp
, B.cpp
. The classes A
and B
never expose the member fooList
. Is it ever possible that since the forward declaration is exactly the same, in files A.h
and B.h
for the struct Foo
, the generated code could be using one or the other. This could explain the issue I was seeing in the linked problem.
In other words, while using the braced-init-list for the struct Foo
invoked in B.cpp
is it guaranteed that Foo
defined in B.cpp
would be used or it is equally likely that Foo
defined in A.cpp
could also be used?
Even as I am writing this, I immediately realize that this implementation is a bad practice as Foo
itself is internal to the classes A
and B
and should really have been declared in the private section of the class itself.
This violates the ODR (one definition rule).
The program is ill-formed, no diagnostic required.
Absolutely any behavior is permitted by the C++ standard if you do this. It could "work", it could pick one and discard the other, it could work until you relink, it could format your hard drive.
I have done this in a real live project; we had a matrix header where you could define tokens before including it if it would support float or double.
This "worked" while we never used both versions in the same DLL. Then we used both versions.
The compiler would pick one size or the other for the structure based on one set of coincidences, and would pick one constructor or other based off a slightly different set of coincidences. We got memory corruption galore. But only sometimes on some builds.
We quickly "fixed" it by wrapping the code in a namespace whose name included the scalar type, then
using
brought them into the exterior namespace.