Is this C++ structure initialization trick safe?

前端 未结 16 1070
有刺的猬
有刺的猬 2020-12-23 17:56

Instead of having to remember to initialize a simple \'C\' structure, I might derive from it and zero it in the constructor like this:

struct MY_STRUCT
{
            


        
相关标签:
16条回答
  • 2020-12-23 18:21

    You can simply value-initialize the base, and all its members will be zero'ed out. This is guaranteed

    struct MY_STRUCT
    {
        int n1;
        int n2;
    };
    
    class CMyStruct : public MY_STRUCT
    {
    public:
        CMyStruct():MY_STRUCT() { }
    };
    

    For this to work, there should be no user declared constructor in the base class, like in your example.

    No nasty memset for that. It's not guaranteed that memset works in your code, even though it should work in practice.

    0 讨论(0)
  • 2020-12-23 18:21

    If MY_STRUCT is your code, and you are happy using a C++ compiler, you can put the constructor there without wrapping in a class:

    struct MY_STRUCT
    {
      int n1;
      int n2;
      MY_STRUCT(): n1(0), n2(0) {}
    };
    

    I'm not sure about efficiency, but I hate doing tricks when you haven't proved efficiency is needed.

    0 讨论(0)
  • 2020-12-23 18:26

    The examples have "unspecified behaviour".

    For a non-POD, the order by which the compiler lays out an object (all bases classes and members) is unspecified (ISO C++ 10/3). Consider the following:

    struct A {
      int i;
    };
    
    class B : public A {       // 'B' is not a POD
    public:
      B ();
    
    private:
      int j;
    };
    

    This can be laid out as:

    [ int i ][ int j ]
    

    Or as:

    [ int j ][ int i ]
    

    Therefore, using memset directly on the address of 'this' is very much unspecified behaviour. One of the answers above, at first glance looks to be safer:

     memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));
    

    I believe, however, that strictly speaking this too results in unspecified behaviour. I cannot find the normative text, however the note in 10/5 says: "A base class subobject may have a layout (3.7) different from the layout of a most derived object of the same type".

    As a result, I compiler could perform space optimizations with the different members:

    struct A {
      char c1;
    };
    
    struct B {
      char c2;
      char c3;
      char c4;
      int i;
    };
    
    class C : public A, public B
    {
    public:
      C () 
      :  c1 (10);
      {
        memset(static_cast<B*>(this), 0, sizeof(B));      
      }
    };
    

    Can be laid out as:

    [ char c1 ] [ char c2, char c3, char c4, int i ]
    

    On a 32 bit system, due to alighments etc. for 'B', sizeof(B) will most likely be 8 bytes. However, sizeof(C) can also be '8' bytes if the compiler packs the data members. Therefore the call to memset might overwrite the value given to 'c1'.

    0 讨论(0)
  • 2020-12-23 18:26

    I assume the structure is provided to you and cannot be modified. If you can change the structure, then the obvious solution is adding a constructor.

    Don't over engineer your code with C++ wrappers when all you want is a simple macro to initialise your structure.

    #include <stdio.h>
    
    #define MY_STRUCT(x) MY_STRUCT x = {0}
    
    struct MY_STRUCT
    {
        int n1;
        int n2;
    };
    
    int main(int argc, char *argv[])
    {
        MY_STRUCT(s);
    
        printf("n1(%d),n2(%d)\n", s.n1, s.n2);
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-23 18:28

    If you already have a constructor, why not just initialize it there with n1=0; n2=0; -- that's certainly the more normal way.

    Edit: Actually, as paercebal has shown, ctor initialization is even better.

    0 讨论(0)
  • 2020-12-23 18:28

    What I do is use aggregate initialization, but only specifying initializers for members I care about, e.g:

    STARTUPINFO si = {
        sizeof si,      /*cb*/
        0,              /*lpReserved*/
        0,              /*lpDesktop*/
        "my window"     /*lpTitle*/
    };
    

    The remaining members will be initialized to zeros of the appropriate type (as in Drealmer's post). Here, you are trusting Microsoft not to gratuitously break compatibility by adding new structure members in the middle (a reasonable assumption). This solution strikes me as optimal - one statement, no classes, no memset, no assumptions about the internal representation of floating point zero or null pointers.

    I think the hacks involving inheritance are horrible style. Public inheritance means IS-A to most readers. Note also that you're inheriting from a class which isn't designed to be a base. As there's no virtual destructor, clients who delete a derived class instance through a pointer to base will invoke undefined behaviour.

    0 讨论(0)
提交回复
热议问题