Is there a way to make a C++ struct value-initialize all POD member variables?
Asked Answered
L

3

6

Suppose I have a C++ struct that has both POD and non-POD member variables:

struct Struct {
    std::string String;
    int Int;
};

and in order for my program to produce reproduceable behavior I want to have all member variables initialized at construction. I can use an initializer list for that:

 Struct::Struct() : Int() {}

the problem is as soon as I need to change my struct and add a new POD member variable(say bool Bool) I risk forgetting to add it to the initializer list. Then the new member variable will not be value-initialized during struct construction.

Also I can't use the memset() trick:

Struct::Struct()
{
   memset( this, 0, sizeof( *this ) ); //can break non-POD member variables
}

because calling memset() to overwrite already constructed non-POD member variables can break those.

Is there a way to enforce value-initialization of all POD member variables without explicitly adding their initialization in this case?

Laurentian answered 14/10, 2010 at 7:5 Comment(11)
I would recommend each member to be const. Especially when they are all public it really makes sense to force immutability. You can use array initialize syntax to create instances: Struct s = { "...", 0 };Buster
@Daniel: And when I want to put it in a container?Diplomatics
@GMan: I would place it in a std::shared_ptr in this case. Or maybe decide to properly encapsulate the members and remove the const.Buster
@Daniel: There's nothing wrong with all public members.Diplomatics
@GMan: I don't think it is a question of wrong or right. I have used the concept of immutability with public members of a struct as a kind of protection for myself. If you can enforce consistent state of a class with public modifiable member variables, then go ahead and do so.Buster
@Daniel: Okay, there's nothing wrong with mutable state. I'm not sure why you're pushing for immutable states, it seems out of the blue.Diplomatics
@GMan: It will force initialization. One way to solve the problem in question.Buster
@Daniel: I guess, not exactly a minor change though. I then reiterate that now it's noncopyable, and if I want it in a container it has to be dynamically allocated. Yuck.Diplomatics
@GMan: You don't necessarily have to agree here. Unless you are the one who posted the question, you don't know much about the situation where this will be applied. Please keep an open-minded attitude towards solutions you, at first sight, don't agree with. Curiousness is one of the trademarks of a good developer, in my opinion.Buster
@Daniel: I don't need you to play arm-chair psychiatrist with me, or call me closed-minded. Very rude. In this situation, given the information we have, making the members all const isn't a proper solution because you've changed what the class can do. There are other solutions that solve the problem that don't change the usability. While this doesn't eliminate the possibility your solution could be applied to different situations (which I never said), it does mean your solution falls a short relative to others.Diplomatics
If only there were a make it a compiler warning to not initialize a member variable in each initializer listBibliotheca
E
10

The cleanest way would be to write the auto-initialzed template class initialized<T>:

EDIT: I realize now it can be made even more flexible by allowing you to declare initialized<Struct>. This means that you can declare initialization without modifying the original Struct. The default initialization 'T()' was inspired on Prasoons answer.

template<class T>  
struct initialized 
{ 
public: 

     initialized() 
        { value = T(); }

    initialized(T t) 
        { value = t; }

    initialized(const initialized<T>& x) 
        { value = x.value; }

    T* operator &() { return &value; } 

     operator T&() { return value; }     

private: 
     T value; 
};


struct PodStruct 
{            
    std::string String;      
    int Int; 
};  


struct GlorifiedPodStruct 
{            
    std::string String;      
    initialized<int> Int; 
};  

void Test()
{
    GlorifiedPodStruct s;
    s.Int = 1;
    int b = s.Int;
    int * pointer = &s.Int;

    initialized<PodStruct> s2;
}

This compiles, but may need more conversion operators, handling of keywords like volatile, etc. But you get the idea.

Equisetum answered 14/10, 2010 at 7:34 Comment(4)
Hmmm. Looks good, but what't the purpose of making value private and then providing full access to it anyway? Wouldn't it be cleaner to just make it public and remove the conversions?Laurentian
@sharptooth: The idea is that you can use initialized<int> almost like an int. For example, initialized<int> a; int b=a; works. Without the conversion operators, you would need to access the value member explicitly.Larisa
@Martin B: Yes, you're right about direct access, I didn't think of that. Still making value private makes no sense.Laurentian
@sharptooth: value being private or not really important. It is just that when initialized<T> is a fully interchangeable with T, you never need to access value anyway.Equisetum
F
10

Linked Question here

Is there a way to enforce value-initialization of all POD member variables without explicitly adding their initialization in this case?

I am not sure whether something like that is possible [directly] or not but the following works

prasoon@prasoon-desktop ~ $ cat check.cpp && clang++ check.cpp && ./a.out
#include <iostream>
struct Struct {
    std::string String;
    int Int;
    bool k;
    // add add add
};

struct InStruct:Struct
{
   InStruct():Struct(){}
};

int main()
{
   InStruct i;
   std::cout<< i.k << "  " << i.Int << std::endl; 
}
0  0
prasoon@prasoon-desktop ~ $ 
Fellmonger answered 14/10, 2010 at 7:16 Comment(3)
-1 You're just lucky and are getting zeros because the memory that i was placed in happened to be zero-initialized. This is not guaranteed by the standard.Larisa
@Martin B : I think it is guaranteed by C++03. Please have a look at my question hereFellmonger
I learned something there -- my apologies. I'm removing the downvote and adding an upvote.Larisa
E
10

The cleanest way would be to write the auto-initialzed template class initialized<T>:

EDIT: I realize now it can be made even more flexible by allowing you to declare initialized<Struct>. This means that you can declare initialization without modifying the original Struct. The default initialization 'T()' was inspired on Prasoons answer.

template<class T>  
struct initialized 
{ 
public: 

     initialized() 
        { value = T(); }

    initialized(T t) 
        { value = t; }

    initialized(const initialized<T>& x) 
        { value = x.value; }

    T* operator &() { return &value; } 

     operator T&() { return value; }     

private: 
     T value; 
};


struct PodStruct 
{            
    std::string String;      
    int Int; 
};  


struct GlorifiedPodStruct 
{            
    std::string String;      
    initialized<int> Int; 
};  

void Test()
{
    GlorifiedPodStruct s;
    s.Int = 1;
    int b = s.Int;
    int * pointer = &s.Int;

    initialized<PodStruct> s2;
}

This compiles, but may need more conversion operators, handling of keywords like volatile, etc. But you get the idea.

Equisetum answered 14/10, 2010 at 7:34 Comment(4)
Hmmm. Looks good, but what't the purpose of making value private and then providing full access to it anyway? Wouldn't it be cleaner to just make it public and remove the conversions?Laurentian
@sharptooth: The idea is that you can use initialized<int> almost like an int. For example, initialized<int> a; int b=a; works. Without the conversion operators, you would need to access the value member explicitly.Larisa
@Martin B: Yes, you're right about direct access, I didn't think of that. Still making value private makes no sense.Laurentian
@sharptooth: value being private or not really important. It is just that when initialized<T> is a fully interchangeable with T, you never need to access value anyway.Equisetum
I
-1

You can add a base struct:

struct PODStruct
{
  PODStruct(unsinged int count) { memset( this, 0, count);}
};

And then your struct derived from this base struct, first place if you have more than one base structs,

struct Struct : PODStruct
{
  Struct();
  std::string Str;
  int Int;
}

Struc::Struct() : PODStruct(sizeof(Struct))
{
}
Incline answered 14/10, 2010 at 8:15 Comment(1)
This leads to UB. You can't memset over a non-POD type, like std::string.Diplomatics

© 2022 - 2024 — McMap. All rights reserved.