1

I am wondering how I can properly initialise a struct in a "C++-way".

I am filling an addrinfo-structure with the relevant information for my hints (to use in getaddrinfo()).

Depending on how I initilise the struct, I will later on be able to get said addrinfo and let the network thing do its networky thing or fail with "unknown error".

The tutorial I took the code from (which was in C as far as I can tell) used this:

struct addrinfo hints;
memset(&hints, 0, sizeof hints);

This seems to be a valid way to initialise the struct of name "hints" with all zeroes. It makes my program work.

Although it appears that memset (if applied incorrectly) is evil, in this case we are filling a "POD"-object, so we should be safe. (https://stackoverflow.com/a/1976024/1968308)

The alternative I tried was using a initialiser (Can you call it a constructor?) as suggested in this answer: https://stackoverflow.com/a/54019987

struct addrinfo hints;
hints = addrinfo{};

(I am aware that I can abbreviate above two lines by initialising while declaring, but the declaration is in the header file for my base class whereas the initialisation happens in each inheriting class)

This produces an error (probably from getaddrinfo).

Since the only thing I changed was the initialisation of hints, the underlying mechanism seems to be the culprit. Can anyone please shed light on what is going on here?

lhiapgpeonk
  • 457
  • 1
  • 5
  • 18
  • Please edit your post with the declaration of the `addrinfo` structure. – Thomas Matthews Mar 11 '22 at 22:23
  • 1
    Starting in C++11, if you wan to value initialize (will zero if no default value if defined), then you want `type vairable_name{};` – NathanOliver Mar 11 '22 at 22:23
  • 1
    @ThomasMatthews: addrinfo resides in netdb.h which is an integral part to some form of standard library, be that C or C++ (you may find the definition here: https://pubs.opengroup.org/onlinepubs/007904875/basedefs/netdb.h.html) – lhiapgpeonk Mar 11 '22 at 22:28
  • just use struct `addrinfo hints{};` it is simple to verify, just write out the contents – AndersK Mar 11 '22 at 22:29
  • @NathanOliver: Well, I have tried that, but that (at least to Visual Studio's mind) gives me a local variable of type addrinfo with the name hints in the current scope. But since I may be doing stuff with hints in the base class, I cannot simply set a new variable. – lhiapgpeonk Mar 11 '22 at 22:30
  • The `memset` technique generally only works for structs containing POD types. Your question (from the title) is about generic `struct`. If you have a `std::string` in your `struct`, then `memset` won't work and you should use a constructor. – Thomas Matthews Mar 11 '22 at 22:30
  • Sidenote: `hints = addrinfo{};` is an assignment, not an initialization, though you could `addrinfo hints = addrinfo{};` and get an initialization that effectively a more verbose version of what Nathan suggested above. Useful in some cases like `auto hints = addrinfo{};` – user4581301 Mar 11 '22 at 22:31
  • avoid memset altogether in C++, then you will not get surprised down the road – AndersK Mar 11 '22 at 22:31
  • @ThomasMatthews: I have ammended the title of the question to narrow the range of answers down. So for my specific problem I am already aware that I can use memset, because addrinfo contains only POD types. (Or is that a misconception? I mean, it works with memset) – lhiapgpeonk Mar 11 '22 at 22:34
  • 2
    Can you make a [mcve] for the error? – HolyBlackCat Mar 11 '22 at 22:34
  • 2
    *declaration is in the header file for my base class whereas the initialisation happens in each inheriting class* Well... sorta. Initialization of the base class members will happen in the base class no matter what you do. Everything you do afterward is doomed to be assignment. Why not zero initialize in the base class and make amendments later in the derived classes? In modern C++ you can often `addrinfo hints = addrinfo{};` right in the class definition – user4581301 Mar 11 '22 at 22:35
  • @user4581301: That seemed to do the trick. Feel free to post this as an answer and I can accept it. What I am still wondering is: Is there no default initialisation in C++ for structs? (I have more of a C# background, so I still have to come to terms with some concepts of C++) – lhiapgpeonk Mar 11 '22 at 22:41
  • C++ Initialization [is disturbingly complicated](https://en.cppreference.com/w/cpp/language/initialization). [The Nightmare of Initialization in C++](https://www.youtube.com/watch?v=7DTlWPgX6zs) is great viewing if you have the interest in seeing how deep the rabbit hole goes and have an hour to kill. – user4581301 Mar 11 '22 at 22:45

2 Answers2

1

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.

user4581301
  • 33,082
  • 7
  • 33
  • 54
1

You are looking for simply hints = {}; No repetition of the type name is needed. If you really want to, then hints = (struct addrinfo){}; works fine as well.

Both of these examples are assignment, not initialization. Initialization only happens during object creation, that means either

(a) prior to the constructor body for non-static data members or

(b) part of the definition, for all other variables or

(c) part of the new-expression, for dynamically-allocated objects


Note that the intialization expression for a non-static data member can be explicitly listed in the ctor-initializer-list or implicitly done there due to the presence of a brace-or-equal-initializer in the definition. But "before execution of the constructor body" is when it actually happens.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720