In C++ the initialization stacks. A class member and all base classes are initialized before the program enters the body of the constructor and will call the appropriate constructors to do so. Everything afterward, including inside the constructor's body, is assignment. To initialize a member you need to use either a default member initializer or a Member Initializer List in the class's constructor.
It's not important to this question, but explicitly initializing in the member initializer list is often superior to default initialization followed by assignment in the constructor body. If the default initialization is expensive, you won't want to perform it and then repeat a lot of the work later with the assignment.
addrinfo
is a C structure, what we used to call a Plain Old Data type, and they are as simple as it gets. They require no specialized initialization and have no constructors and nothing they contain does either. So no initialization is performed. Their initial values are undefined and tend to reflect whatever happened to be in the memory location where the addrinfo
now sits, basically garbage. As the asker found, this "garbage" is detrimental and needs to be cleared.
Simple code example:
class info_base
{
protected:
addrinfo info = addrinfo(); // default member initializer
// will fall back on zero initialization in this case
};
class info_tcp:public info_base
{
public:
info_tcp() // initialization includes default initialization of info_base
// which will use the default member initializer
{
info.ai_socktype = SOCK_STREAM;
info.ai_protocol = IPPROTO_TCP;
}
};
One of the advantages of being so simple is it can be aggregate initialized and as of C++20 we can do simple stuff like this (Note there is no noticeable performance advantage to this--far as I can see--but knowing you can do this could come in handy later)
class info_base
{
public: // note public now. Necessary to be an aggregate
addrinfo info = addrinfo();
};
class info_tcp:public info_base
{
public:
info_tcp(): info_base({.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP})
// Aggregate initialization of info_base via member
// Initializer List taking advantage of designated
// initializers added in C++20
{
}
};
Because info_base
and its info
member are being explicitly initialized, the default member initializer is skipped. I don't much like this one because it made info
public
and now any shmuck can mess with it, but it is really simple.
class info_base
{
protected:
addrinfo info;
info_base(int type,
int protocol): info {.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP}
{
}
};
class info_tcp:public info_base
{
public:
info_tcp(): info_base(SOCK_STREAM, IPPROTO_TCP)
{
}
};
Now info_base
doesn't have a default constructor, you can add it back if you like, but the caller can specify the important parameters to use to initialize info
and no one outside of the family can interact with addr
.