0

According to cppreference, a trivially copyable class should:

(class) has no virtual member functions;
(class) has no virtual base classes;

I don't understand the reason behind these requirements.

I tried to figure it out myself by doing:

#include <iostream>

struct virt
{
    int q;
    void virtual virt_func()
    {
        q += 2;
        std::cout << "base implementation: object value " << q << std::endl;
    }
};

struct virt_1 : public virt
{
    float w;
    void virt_func() override
    {
        w += 2.3;
        std::cout << "child 1 implementation: object value " << w << std::endl;
    }
};

struct virt_2 : public virt_1
{
    double e;
    void virt_func() override
    {
        e += 9.3;
        std::cout << "child 2 implementation: object value " << e << std::endl;
    }
};

int main()
{
    virt_2 * t = new virt_2();
    t->virt_func();
    void * p = malloc(sizeof(virt_2));
    
    memmove(p, t, sizeof(virt_2));

    static_cast<virt_2 *>(p)->virt_func();

    std::cout <<"End of a file" << std::endl;
    return 0;
}

and it works as it should, by printing:

child 2 implementation: object value 9.3
child 2 implementation: object value 18.6
End of a file

So, why, is what is effectively is a, no vtable pointer requirement is there? I mean, it's a simple pointer that can (should) be copied without any problem at all, right?!

Remy Lebeau
  • 505,946
  • 29
  • 409
  • 696
  • 1
    What should happen if the vtable pointer isn't the correct pointer for the type being copied? It is an incorrect assumption that whatever vtable pointer exists in a `BaseClass&` is the vtable pointer for `BaseClass`. – Drew Dormann Apr 08 '22 at 16:13
  • 4
    Don't expect anything from _undefined behavior_ like you're using. – πάντα ῥεῖ Apr 08 '22 at 16:14
  • 4
    You have an assumption that `virtual` functions are implemented using vtable pointer, which is not guaranteed by standard. – Yksisarvinen Apr 08 '22 at 16:16
  • [std::memmove](https://en.cppreference.com/w/cpp/string/byte/memmove): "If the objects are potentially-overlapping or not TriviallyCopyable, the behavior of memmove is not specified and may be *undefined*." – Kevin Apr 08 '22 at 16:19
  • 1
    you cannot proovde or disproove the presence of UB by looking out output of some code, because UB means that the output can be anything, including what you expect – 463035818_is_not_a_number Apr 08 '22 at 16:19
  • 2
    Does this answer your question? [Why would the behavior of std::memcpy be undefined for objects that are not TriviallyCopyable?](https://stackoverflow.com/questions/29777492/why-would-the-behavior-of-stdmemcpy-be-undefined-for-objects-that-are-not-triv) – Kevin Apr 08 '22 at 16:19
  • The cool thing about UB is you absolutely can exploit it, but only when you fully understand how it will behave on the target, after you've tested the ever-loving smurf out of it to ensure there are no gotrchas you missed in your analysis, and it's the last viable resort. Also helps if there can't be any bad consequences like death and dismemberment if it turn out you're still wrong. – user4581301 Apr 08 '22 at 16:21
  • I undid my duplicate close vote (there may be a better duplicate question though). The question is asking why a trivially copyable object can't have virtual functions/bases, not why `std::memmove` requires objects to be trivially copyable. – Kevin Apr 08 '22 at 16:23
  • 1
    Trivially copyable, among other things, allows "object can be written to a file, read from that file by another program, and the original object will be reconstructed". While the pointer may be correctly received, what that pointer points at may be completely different in the second program. – Peter Apr 08 '22 at 18:33

1 Answers1

2

Your example doesn't illustrate the dangers.

Imagine this:

virt_1 *copy_v1(virt_1 *t)
{
    void * p = malloc(sizeof(virt_1));
    memmove(p, t, sizeof(virt_1));
    return (virt_1 *)p;
}

called like this:

int main()
{
    virt_2 * v2 = new virt_2();
    virt_1 * v1 = copy_v1(v2);
    v1->virt_func();
}
dirck
  • 312
  • 1
  • 6
  • 2
    and what does this illustrate? – 463035818_is_not_a_number Apr 08 '22 at 16:24
  • So if I ensures (somehow) that there are wouldnt be any slicing of an object or moving over its hierarchy type it will be 'fine'? – DisplayName Apr 08 '22 at 16:29
  • 2
    This code is compilable and will fail in some way, possibly catastrophically - you can try it. The problem with copy_v1 is that you can pass an instance of any child class of virt_1 and the copy will fail if the sizeof(child) != sizeof(virt_1). Inside copy_v1 you don't know the actual type of the object passed, so you can't copy the bytes - you don't know how many to copy. You've copied the vtable and functions in the vtable may reference memory off the end of the (partially) copied instance. – dirck Apr 08 '22 at 16:36