0

When I do sizeof in c++, will I be sure to get the "whole object"? I am asking because I am about to copy objects to other areas of memory using memcpy (probably a stupid idea from the start, right?).

What I am worried about is that I may not get the whole object, but only the parts belonging to the class it is casted to right now. Does it make any sense or am I being confused?

EDIT Examples

 class A{ public:  int a = 123; };
 class B: public A{ public: int b = 321; };
 class C : public B{ public: int c = 333; };

 C c_ = C();
 B b_ = C();
 A a_ = C();

std::cout << sizeof(a_) << " , " << sizeof(b_) << " , " << sizeof(c_) << std::endl;

Seems to give me 4,8,12.

I guess I would need to do dynamic casting to figure out how to get the "whole" object which I constructed as a "C" class in each case?

mathreadler
  • 447
  • 5
  • 16
  • 1
    Did you try to print these sizes? Or to use a debugger? Of course `sizeof(b)` is bigger (probably by 4 bytes) than `sizeof(a`) – Basile Starynkevitch Feb 26 '18 at 06:25
  • 1
    Probably because of *padding*. See the very related (if not duplicate?) [Why isn't sizeof for a struct equal to the sum of sizeof of each member?](https://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member) – Some programmer dude Feb 26 '18 at 06:26
  • @BasileStarynkevitch ok they seem to both be 8 for me when debugging in VS in windows. I wonder if it is because it understands that B is an A or because of padding as the other guy mentioned. A modern machine should be 64 bits = 8 bytes, so therefore it pads to fit the bit width? – mathreadler Feb 26 '18 at 06:27
  • 3
    And yes, doing `memcpy` is usually a very bad idea in C++, except for plain old data (POD) structures. – Some programmer dude Feb 26 '18 at 06:28
  • 1
    It may help to not think if "casting" but of the actual type of the objects. `a` is of type `A`. `b` is of type `B`. It does not matter how an `A` object is created, its `sizeof` is a constant. – juanchopanza Feb 26 '18 at 06:30
  • 1
    `memcpy` is ok if the objects are TriviallyCopyable (e.g. scalars, arrays, C-compatible structs). For objects that are not TriviallyCopyable, there is a very strong chance that you will run into undefined behavior unless you know exactly what you are doing. – R Sahu Feb 26 '18 at 06:33
  • Yes, the `sizeof` operator gets the size of the "whole object" i.e. of its operand. Yes, copying objects using `memcpy()` is, unless you know what you are doing and only use `memcpy()` on a trivially copyable object, a pretty silly idea in C++ since there are several circumstances in which using such a copy of an object gives undefined behaviour. No, `sizeof(b)` and `sizeof(a)` will usually differ in your example. – Peter Feb 26 '18 at 06:33
  • (Since you have now edited to make some preceding comments meaningless) - using `dynamic_cast` won't get the "whole objects" in your (edited) example. Object slicing is in play. `b_` and `a_` are not instances of `C` - they have been initialised using part of a default-constructed `C`. – Peter Feb 26 '18 at 06:58
  • Yes sorry it is a bit bad style to edit like that, but I realized my initial question was not good enough. Okay it seems i should read up on more of these things, thank you @Peter. – mathreadler Feb 26 '18 at 07:04
  • Thank you very much for your patience and time, guys. I am starting to think that "new with placement" is a healhier way to do what I was about to do. – mathreadler Feb 26 '18 at 07:07
  • Careful, placement `new` requires you to comply with the alignment requirements of your type - you cannot just pass in whatever slab of bytes you happen to have available. – Matteo Italia Feb 26 '18 at 07:55
  • @MatteoItalia okay, but will I be safe if I check with alignof? – mathreadler Feb 26 '18 at 07:59
  • It should be; also, if you allocate with `malloc` it'll be safe for any type containing the regular primitive types (and aggregates thereof). But we could give you better suggestions if you explained what you are trying to do. – Matteo Italia Feb 26 '18 at 08:05

1 Answers1

5

sizeof will always return the static size of your object. Notice that in your example it will coincide with the true object size, as there is no polymorphism; when you do

A a = B();

a is of type A - you just happened to initialize a new A object with a new B object, which results in slicing (a gets initialized with the fields of B() that are common with A).

A better example would be:

B b;
A *a = &b;

In this case, *a will indeed be of dynamic type B, but sizeof(*a) will still return sizeof(A), not sizeof(B).

There are several ways to obtain the dynamic size of an object:

  • save it into a field at construction time;
  • in theory, you could define a virtual method that does return sizeof(*this); and redefine it in all derived classes.

That being said, this last method won't be particularly useful, as doing memcpy of non-trivial types such as polymorphic classes is undefined behavior (and so even the first method as well, as I imagine that you'll want to do this with polymorphic types).


The common approach to the problem of copying polymorphic classes is to accept the fact that they'll have to live in the heap and define clone() method that does virtual A * clone() {return new B(*this);} (where B is the derived class) in each derived class, and invoke clone() whenever you need a copy.

Mind you, there are subtler tricks you can pull; once I had a class hierarchy which had a virtual method dispatching to the placement new for each derived class and one for the destructor, but you really have to know what you are doing (in my case I was invoking them over a union containing an instance for each derived class, so size and alignment was not a problem).

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • Ah okay, but if I do a dynamic_cast(a) after A* a = &b; will I then get the B aspects of a? – mathreadler Feb 26 '18 at 07:06
  • 2
    Then you'll get `sizeof(B*)`; if you do `sizeof(*dynamic_cast(a))` you'll get `sizeof(B)`, whatever the cast returns. Even if you check what it may return, it'll return non-NULL values for whatever class is derived from `B` (not just `B`), so the actual size may be bigger. To have an exact type match you'll have to use `typeid`, but since all of `typeid`, `dynamic_cast` and virtual methods require a polymorphic class, you cannot use any of them with `memcpy`. – Matteo Italia Feb 26 '18 at 07:17