3

Lets say I have a class

struct Foo {
    uint32_t x;
    uint32_t y;
};

does the C++ standard make any mention whether sizeof(Foo) should be the same as sizeof(Bar) if Bar just adds a constructor ?

struct Bar {
    uint32_t x;
    uint32_t y;
    Bar(uint32_t a = 1,uint32_t b = 2) : x(a),y(b) {}
};

The reason why I am asking is that Foo is send across network as a void* and I cannot change its size, but if possible I would like to add a constructor.

I found some related: here and here, but there the answers focus mainly on virtuals changing the size and I was looking for something more definite than "[...] all implementations I know of [...]".

PS: To avoid misunderstanding... I am not asking how to constsruct a Foo and then send it as a void* nor am I asking for a workaround to make sure the size does not change, but I am really just curious, whether the standard says anything about sizeof in that specific case.

Community
  • 1
  • 1
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 1
    Even if there were no guarantees in the standard on this, no sane compiler would yield a different size for the second case: There is simply nothing to be stored within the `struct` to add a constructor. – cmaster - reinstate monica Apr 07 '17 at 10:28
  • @cmaster sure, I also wouldnt know any reason why the `sizeof` should be different, but whether this is guaranteed by the standard is a different story – 463035818_is_not_an_ai Apr 07 '17 at 11:26
  • Well, if the `sizeof` is your sole concern, you might just add a `static_assert()` on it. – cmaster - reinstate monica Apr 07 '17 at 11:48
  • @cmaster not sure what you mean. Once I change to `Bar`, there wont be any `Foo` anymore. Anyhow, I was not asking for a practical solution, but was really just curious what the standard has to say about it – 463035818_is_not_an_ai Apr 07 '17 at 11:56
  • *"Foo is send across network as a void*"*, You risk to have issue with endianness... – Jarod42 Apr 07 '17 at 12:01
  • Ok, more detail for this precise situation: I would keep the `struct Foo` (the definition only, the other code uses `struct Bar`), because that is your over-the-network format which must be kept stable. Add some explaining comments to it as well. Then add a `static_assert(sizeof(struct Foo) == sizeof(struct Bar))`, along with some asserts like `static_assert(offsetof(struct Foo), x) == offsetof(struct Bar), x))` That gives you the security that the compiler will bark immediately if you make any changes to `struct Bar` that break binary compatibility. – cmaster - reinstate monica Apr 07 '17 at 12:09
  • @Jarod42 endianness is taken care of just not shown here. I wonder if I should remove the "The reason why I am asking this..." part, because it mainly distracts from the actual question.... – 463035818_is_not_an_ai Apr 07 '17 at 12:09
  • @cmaster thanks for the input. However, I already had a solution before asking the question and just wanted to know what is the official answer to whether the size may change or not. Sorry if this wasnt clear – 463035818_is_not_an_ai Apr 07 '17 at 12:12

3 Answers3

4

C++ 98 only guarantees layout for “plain old data” objects and those don't permit constructors.

C++ 11 introduces “standard layout types”, which still guarantee layout, but do permit constructors and methods to be added (and permits non-virtual bases to be added with some exceptions for empty classes and duplicates).

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • do I understand it correctly that in C++11 i can safely cast a `Foo*` to a `Bar*` but in C++98 this is not guaranteed to work by the standard? – 463035818_is_not_an_ai Apr 07 '17 at 10:18
  • 2
    @tobi303 it only works if the pointer points to a member of a union containing those two things... otherwise it is a strict aliasing violation – M.M Apr 07 '17 at 10:19
  • @M.M omg that sound complicated, I think I will just go for a free `createFoo()`. Anyhow seems that I have to do some reasearch to understand the issue – 463035818_is_not_an_ai Apr 07 '17 at 10:20
  • @tobi303 The cleanest solution is to not send structs across the network; instead use data serialization – M.M Apr 07 '17 at 10:22
  • @M.M its a huge legacy code, so I am already happy if I can make it a bit cleaner ;) Thanks for the tip – 463035818_is_not_an_ai Apr 07 '17 at 10:23
  • @tobi303, C++11 just extends the scope where it is possible from only structs without bases and methods to most structs without virtual bases and members (with a couple of exceptions). – Jan Hudec Apr 07 '17 at 10:23
  • @M.M, I believe the [strict aliasing rules](http://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing) actually do permit access to the common members of `Foo` and `Bar` for which the standard layout defines the same content, plus `char *` is explicitly exempt from strict aliasing and you cast to `char *` for `write` to socket and back from `char *` after `read`. – Jan Hudec Apr 07 '17 at 10:27
  • @JanHudec _"... and back from char *"_ it's this bit I have problems with. My reading of the standard is that I can't cast back(from `char *`) in a different program from where the initial object (of say type `T`) was created. Unless the underlying buffer on the receiving side is already type `T` (in which case the cast is not needed). – Richard Critten Apr 07 '17 at 10:38
  • @RichardCritten, actually, yes, but it is not a problem. Because what you do is allocate a `Foo` (array) and pass it to `read` as `char *` to fill. – Jan Hudec Apr 07 '17 at 10:47
1

Actually, the only thing that influences the layout is the data contained in an object -- with one important exception, coming to that later. If you add any functions (and constructors in reality are nothing more than some kind of static function only with special syntax), you do not influence the layout of the class.

If you have a virtual class (a function with at least one virtual function, including a virtual destructor), your class will contain an entry to a vtable (this is not enforced by the standard, but that's the standard way how polymorphism is implemented), but this is just a pointer to some specific memory location elsewhere. The vtable itself will be modified, if you add more virtual functions, but without any influence on the layout of your data containers (which your class instances actually are).

Now the exception mentioned above: If you add a virtual function to a class (or make an existing one virtual, including the destructor) while the class did not have any virtual functions before (i. e. no own virtual functions and no inherited ones!), then a vtable will be newly added and then the data layout does change! (Analogously, the vtable pointer is removed if you make all functions non-virtual - including all inherited ones).

Guaranteed by the standard?

Edit:

From C++ standard, section 4.5 The C++ object model (§ 1):

[...] Note: A function is not an object, regardless of whether or not it occupies storage in the way that objects do. — end note [...]

Next is deduction (of mine): A function (note: not differentiated if free or member one) is not an object, thus cannot be a subobject, thus is not part of the data of an object.

Further (same §):

An object has a type (6.9). Some objects are polymorphic (13.3); the implementation generates information associated with each such object that makes it possible to determine that object’s type during program execution.

(That is the vtables! - note that it is not explicit about how they are implemented, does not even enforce them at all, if a compiler vendor finds some alternative, it is free to use it...).

For other objects, the interpretation of the values found therein is determined by the type of the expressions (Clause 8) used to access them.

Well, couldn't find any hints (so far), if or how functions influence the layout of a class, not flying over the standard as a whole, not (with special attendance) in chapters 8 (as referenced above) or 12 "Classes" (especially 12.2 "Class members").

Seems as this is not explicitly specified (won't hold my hand into fire for not having overseen, though...). Maybe it is valid to deduce this already from functions not being objects solely...

Standard layout classes, as referenced by Jan Husec, provide further guarantees on layout, such as no reordering of members (which is allowed for members of different accessibility), alignment conditions, ...

From those conditions for being a SLC, I deduce that for these, at least, the guarantee applies, as all that is referenced for being layout compatible is the data members, no mention of (non-static member) functions (other than no virtual ones being allowed...).

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Still, the first `virtual` keyword *does* change the size of the `struct`. – cmaster - reinstate monica Apr 07 '17 at 10:30
  • @cmaster Hm, thought I covered that by differentiating between virtual and non-virtual classes... Now being more explicit about making a non-virtual class virtual. – Aconcagua Apr 07 '17 at 10:41
  • sorry, but apart from the "see Jan Hudec's answer" this does not help me, as I know (more or less) what happens when I add a virtual function, but thats not relevant here – 463035818_is_not_an_ai Apr 07 '17 at 11:24
  • @tobi303 My point is: **Unless** you make a non-virtual class virtual or the other way round (which adds/removes the vtable), *any* adding or removing of functions (which ctors in the end are, too) will **not** change the memory layout (at least none of any compilers I know of does...). – Aconcagua Apr 07 '17 at 12:37
  • I wanted to know what the stadard says not what compilers say about it....anyhow thanks for the input – 463035818_is_not_an_ai Apr 07 '17 at 12:43
0

As explained by other answers the layout can change in case a virtual destructor is added.

If your destructor is not virtual you can go ahead and add. But say you need a virtual destructor, you can wrap the struct in another struct and place the destructor there.

Make sure the fields that you want to modify are accessible to the wrapper class. A friend relation should be good enough.

Although all your inheritance will be through the wrapper. The inner struct is just to maintain the layout so you can send it over the network.

Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • ["virtual constructor"???](http://stackoverflow.com/questions/733360/why-do-we-not-have-a-virtual-constructor-in-c) – Aconcagua Apr 07 '17 at 10:47
  • I am sorry, would apply to a destructor. – Ajay Brahmakshatriya Apr 07 '17 at 10:48
  • how is anything in your answer related to my question? I neither have a virtual destructor, nor wrapper or friend and there is also no inheritance – 463035818_is_not_an_ai Apr 07 '17 at 11:22
  • I know there is no wrapper. I am suggesting to use a wrapper, if there is a virtual destructor. In case there is no virtual destructor, layout won't change. I was just explaining the possible scenarios. Lastly about inheritance. It comes into picture if you have a virtual destructor (else why would you add a destructor)? – Ajay Brahmakshatriya Apr 07 '17 at 11:26
  • sorry, maybe I shouldnt have mentioned my use case at all in the question. I was not asking for a workaround in that specific case (I had it already), but I really was just curious what the standard say about this specific case (ie adding only a constructor, no virtuals, no desctructor involved) – 463035818_is_not_an_ai Apr 07 '17 at 11:28
  • I am sorry if I went beyond and explained for the scenario which you might face in the future because I saw that was missing in the other answers. – Ajay Brahmakshatriya Apr 07 '17 at 11:31