I have a struct that contains multiple members.
these members should be constructed using another member.
Is accessing this other member for the initialization of the members valid, or am I invoking UB this way?
struct Data {
int b;
};
struct Bar {
Bar(Data& d): a(d.b){
}
int a;
};
struct Foo {
Data data;
Bar b;
};
int main() {
Foo f {.data = Data(), .b = Bar(f.data)}; // b is constructed using f.data!
}
https://godbolt.org/z/fajPjo6oa
Members are initialized in the order they are declared in the struct/class and you can validly reference other members during initialization, as long as they have already been initialized at that point.
This holds regardless of how initialization is performed.
Related
OK, member variables can be used to initialize other member variables in an initialization list (with care taken about the initialization order etc). What about member functions? To be specific, is this snippet legal according to the C++ standard?
struct foo{
foo(const size_t N) : N_(N), arr_(fill_arr(N)) {
//arr_ = fill_arr(N); // or should I fall back to this one?
}
std::vector<double> fill_arr(const size_t N){
std::vector<double> arr(N);
// fill in the vector somehow
return arr;
}
size_t N_;
std::vector<double> arr_;
// other stuff
};
Yes, your use of member function in initialization list is valid and complies with the standard.
Data members are initialized in the order of their declaration (and that's the reason why they should appear in the initialization list in the order of their declaration - the rule that you followed in your example). N_ is initialized first and you could have passed this data member to fill_arr. fill_arr is called before constructor but because this function does not access uninitialized data members (it does not access data members at all) its call is considered safe.
Here are some relevant excepts from the latest draft (N3242=11-0012) of the C++ standard:
§ 12.6.2.13: Member functions (including virtual member functions,
10.3) can be called for an object under construction.(...) However, if these operations are performed in a ctor-initializer (or in a function
called directly or indirectly from a ctor-initializer) before all the
mem-initializers for base classes have completed, the result of the
operation is undefined. Example:
class A { public: A(int); };
class B : public A {
int j;
public:
int f();
B() : A(f()), // undefined: calls member function
// but base A not yet initialized
j(f()) { } // well-defined: bases are all initialized
};
class C {
public:
C(int);
};
class D : public B, C {
int i;
public:
D() : C(f()), // undefined: calls member function
// but base C not yet initialized
i(f()) { } // well-defined: bases are all initialized
};
§12.7.1: For an object with a non-trivial constructor, referring to
any non-static member or base class of the object before the
constructor begins execution results in undefined behavior. Example
struct W { int j; };
struct X : public virtual W { };
struct Y {
int *p;
X x;
Y() : p(&x.j) { // undefined, x is not yet constructed
}
};
While initializing objects in the initialization list, the object is not yet fully constructed.
If those function tries to access the part of the object which is not yet constructed then that is a undefined behavior else its fine.
see this answer.
Are there any differences between the following three structure definitions according to the C++ standard?
struct Foo
{
int a;
};
struct Foo
{
int a{};
};
struct Foo
{
int a{0};
};
The last two are C++11.
Given the first definition, if you create an instance of Foo with automatic storage duration, a will be uninitialized. You can perform aggregate initialization to initialize it.
Foo f{0}; // a is initialized to 0
The second and third definitions of Foo will both initialize the data member a to 0.
In C++11, neither 2 nor 3 are aggregates, but C++14 changes that rule so that they both remain aggregates despite adding the brace-or-equal-initializer.
struct Foo
{
int a;
}bar;
bar.a is uninitialized if not in global scope or not-static.
struct Foo
{
int a{};
}bar;
bar.a is initialized to 0
struct Foo
{
int a{0};
}bar;
bar.a is initialized to 0
So constructs 2 and 3 are same. 1 is different.
For more details, you may want to read Initialization and Class Member Initialization
First one is POD type. Member a is initialized by 0.
The compiler seems to have no problem with this. Can I safely assume that any object I create of this type will have these defaults values?
struct ColorProperties
{
bool colorRed = true;
bool colorBlue = false;
bool isRectangle = true;
};
ColorProperties myProperties;
Will myProperties automatically contain element values as noted by the struct?
Yes, you can. It's C++11 feature. Really it's equal to
struct ColorProperties {
ColorProperties()
: colorRed(true), colorBlue(false), isRectangle(true)
{}
//
};
You can read about this proposal here
Quotes from standard.
n3376 12.6.2/8
In a non-delegating constructor, if a given non-static data member or base class is not designated by a
mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no
ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then
— if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized
as specified in 8.5;
struct A {
A();
};
struct B {
B(int);
};
struct C {
C() { }
A a;
const B b; // error: B has no default constructor
int i; // OK: i has indeterminate value
int j = 5; // OK: j has the value 5
};
OK, member variables can be used to initialize other member variables in an initialization list (with care taken about the initialization order etc). What about member functions? To be specific, is this snippet legal according to the C++ standard?
struct foo{
foo(const size_t N) : N_(N), arr_(fill_arr(N)) {
//arr_ = fill_arr(N); // or should I fall back to this one?
}
std::vector<double> fill_arr(const size_t N){
std::vector<double> arr(N);
// fill in the vector somehow
return arr;
}
size_t N_;
std::vector<double> arr_;
// other stuff
};
Yes, your use of member function in initialization list is valid and complies with the standard.
Data members are initialized in the order of their declaration (and that's the reason why they should appear in the initialization list in the order of their declaration - the rule that you followed in your example). N_ is initialized first and you could have passed this data member to fill_arr. fill_arr is called before constructor but because this function does not access uninitialized data members (it does not access data members at all) its call is considered safe.
Here are some relevant excepts from the latest draft (N3242=11-0012) of the C++ standard:
§ 12.6.2.13: Member functions (including virtual member functions,
10.3) can be called for an object under construction.(...) However, if these operations are performed in a ctor-initializer (or in a function
called directly or indirectly from a ctor-initializer) before all the
mem-initializers for base classes have completed, the result of the
operation is undefined. Example:
class A { public: A(int); };
class B : public A {
int j;
public:
int f();
B() : A(f()), // undefined: calls member function
// but base A not yet initialized
j(f()) { } // well-defined: bases are all initialized
};
class C {
public:
C(int);
};
class D : public B, C {
int i;
public:
D() : C(f()), // undefined: calls member function
// but base C not yet initialized
i(f()) { } // well-defined: bases are all initialized
};
§12.7.1: For an object with a non-trivial constructor, referring to
any non-static member or base class of the object before the
constructor begins execution results in undefined behavior. Example
struct W { int j; };
struct X : public virtual W { };
struct Y {
int *p;
X x;
Y() : p(&x.j) { // undefined, x is not yet constructed
}
};
While initializing objects in the initialization list, the object is not yet fully constructed.
If those function tries to access the part of the object which is not yet constructed then that is a undefined behavior else its fine.
see this answer.
I'm updating a struct of mine and I was wanting to add a std::string member to it. The original struct looks like this:
struct Value {
uint64_t lastUpdated;
union {
uint64_t ui;
int64_t i;
float f;
bool b;
};
};
Just adding a std::string member to the union, of course, causes a compile error, because one would normally need to add the non-trivial constructors of the object. In the case of std::string (text from informit.com)
Since std::string defines all of the six special member functions, U will have an implicitly deleted default constructor, copy constructor, copy assignment operator, move constructor, move assignment operator and destructor. Effectively, this means that you can't create instances of U unless you define some, or all of the special member functions explicitly.
Then the website goes on to give the following sample code:
union U
{
int a;
int b;
string s;
U();
~U();
};
However, I'm using an anonymous union within a struct. I asked ##C++ on freenode and they told me the correct way to do that was to put the constructor in the struct instead and gave me this example code:
#include <new>
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
struct Foo
{
Foo() { new(&p) Point(); }
union {
int z;
double w;
Point p;
};
};
int main(void)
{
}
But from there I can't figure how to make the rest of the special functions that std::string needs defined, and moreover, I'm not entirely clear on how the ctor in that example is working.
Can I get someone to explain this to me a bit clearer?
There is no need for placement new here.
Variant members won't be initialized by the compiler-generated constructor, but there should be no trouble picking one and initializing it using the normal ctor-initializer-list. Members declared inside anonymous unions are actually members of the containing class, and can be initialized in the containing class's constructor.
This behavior is described in section 9.5. [class.union]:
A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members. If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X.
and in section 12.6.2 [class.base.init]:
A ctor-initializer may initialize a variant member of the constructor’s class. If a ctor-initializer specifies more than one mem-initializer for the same member or for the same base class, the ctor-initializer is ill-formed.
So the code can be simply:
#include <new>
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
struct Foo
{
Foo() : p() {} // usual everyday initialization in the ctor-initializer
union {
int z;
double w;
Point p;
};
};
int main(void)
{
}
Of course, placement new should still be used when vivifying a variant member other than the other initialized in the constructor.
That new (&p) Point() example is a call to the Standard placement new operator (via a placement new expression), hence why you need to include <new>. That particular operator is special in that it does not allocate memory, it only returns what you passed to it (in this case it's the &p parameter). The net result of the expression is that an object has been constructed.
If you combine this syntax with explicit destructor calls then you can achieve complete control over the lifetime of an object:
// Let's assume storage_type is a type
// that is appropriate for our purposes
storage_type storage;
std::string* p = new (&storage) std::string;
// p now points to an std::string that resides in our storage
// it was default constructed
// *p can now be used like any other string
*p = "foo";
// Needed to get around a quirk of the language
using string_type = std::string;
// We now explicitly destroy it:
p->~string_type();
// Not possible:
// p->~std::string();
// This did nothing to our storage however
// We can even reuse it
p = new (&storage) std::string("foo");
// Let's not forget to destroy our newest object
p->~string_type();
When and where you should construct and destroy the std::string member (let's call it s) in your Value class depends on your usage pattern for s. In this minimal example you never construct (and hence destruct) it in the special members:
struct Value {
Value() {}
Value(Value const&) = delete;
Value& operator=(Value const&) = delete;
Value(Value&&) = delete;
Value& operator=(Value&&) = delete;
~Value() {}
uint64_t lastUpdated;
union {
uint64_t ui;
int64_t i;
float f;
bool b;
std::string s;
};
};
The following is thus a valid use of Value:
Value v;
new (&v.s) std::string("foo");
something_taking_a_string(v.s);
using string_type = std::string;
v.s.~string_type();
As you may have noticed, I disabled copying and moving Value. The reason for that is that we can't copy or move the appropriate active member of the union without knowing which one it is that is active, if any.