2

The c++ standard (and several SO answers) states that to qualify as is_trivially_copyable<T>, a type T must have:

  1. A default destructor,
  2. No virtual functions,
  3. No virtual base classes.

(These are not the only requirements, but the question focuses on these alone)

Can someone shed any light on why? I don't see how violating any of these 3 makes an array of T's unsafe for memcpy.

Community
  • 1
  • 1
Ofek Shilon
  • 14,734
  • 5
  • 67
  • 101
  • Think of a virtual assignnment operator (though not used in sane programs) – Mohit Jain Jul 15 '15 at 07:36
  • What `is_trivially_copyable` has to do with `memcpy`? – 101010 Jul 15 '15 at 07:37
  • @Mohit Jain: these are not the only requirement - lack of non default assignment is a separate one, which I understand. – Ofek Shilon Jul 15 '15 at 07:37
  • 1
    @101010: is_trivially_copyable is advertised as the test to perform before memcpy-ing. – Ofek Shilon Jul 15 '15 at 07:38
  • @OfekShilon I gave argument supporting one of the requirements. Also virtual function and virtual base may involve pointer in object making serialization non-sensical. – Mohit Jain Jul 15 '15 at 07:39
  • @Mohit Jain: perhaps I wasn't clear. Beyond these 3 requirements, an is_trivially_copyable type must have no non-trivial copy assignment operators (13.5.3, 12.8), either virtual or not. A type with virtual assignment operator would fail to qualify as is_trivially_copyable regardless of *these* 3 conditions. – Ofek Shilon Jul 15 '15 at 07:41
  • @OfekShilon I know the requirements and understand why points #2 and #3 mentioned by you are important. And requirement #1 also somehow **may** boil down to virtual destructors and thus pointers. – Mohit Jain Jul 15 '15 at 07:42
  • @MohitJain: I'm sure you do, please don't be *that* quick to take offense. The question relates to the rationale behind the requirements. And note that this trait under discussion is is_trivially_copyable, not is_trivially_serializable. – Ofek Shilon Jul 15 '15 at 07:46
  • @OfekShilon Sorry if it sounds insulting to you. The point is I did't know the exact answer and started some discussion thinking it would be healthy. Anyways never mind. – Mohit Jain Jul 15 '15 at 07:48
  • @MohitJain: why is memcpy-ing a class with a pointer to a vtable nonsensical? It is not a pointer to some internal buffer, and is identical for all identical objects in the same process. – Ofek Shilon Jul 15 '15 at 07:49
  • 1
    [N2762](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2762.htm) seems to be the proposal that introduced the term "trivially copyable". – cpplearner Jul 15 '15 at 08:36
  • Related: [Why would the behavior of std::memcpy be undefined for objects that are not TriviallyCopyable?](http://stackoverflow.com/questions/29777492) – dyp Jul 15 '15 at 11:19

1 Answers1

6

With regard to 1 ("a default destructor"), it's simply because memcpy of a new object into an existing variable won't call the destructor of what it's overwriting, so if the class depends on anything in that destructor, its constraints may be violated.

With regard to 2 ("no virtual functions"), it's likely that the reasoning is that when object slicing occurs, the sliced object must function correctly as the base class object.

Imagine a base and a derived class thus:

class Base {
    int b;
    virtual void f() { ++b; }
}

class Derived : public Base {
    int d;
    void f() override { ++d; }
}

Now suppose you have a Base& variable v that actually references a Derived object. If std::is_trivially_copyable<Base> were true, you could memcpy from this variable to another Base object w (this would copy b and the vtable). If you were now to call w.f(), you would call (through the vtable) Derived::f(). Which of course would be undefined, as w.d has no storage allocated.

This may account also for 3 ("no virtual base classes"), but since I pretty much never use virtual base classes, I'll defer to someone more familiar with them.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • 1
    Just a minor correction to your code example, you forgot to derive `Derived` from `Base`. Besides that, +1 for your answer to requirement 2. I figured it had something to do with slicing, I'd forgotten that virtual functions aren't broken by that occurrence when copy constructors are involved. As an aside, I don't believe the `is_trivially_copyable` trait requires default construction. I couldn't find any reference in the standard(N3337) that said it was a requirement. – Jared Mulconry Jul 15 '15 at 09:49
  • 1
    You appear to be correct in the case of the virtual base classes. A quick inspection of a simple case compiled in VS2015RC showed that the presence of a virtual base class introduced a pointer to a virtual base table that was only present in the deriving class. It seems like hideous slicing effects would ensue. – Jared Mulconry Jul 15 '15 at 10:00
  • Thanks for the correction, @Jared - I did overlook the actual inheritance. Good thing I used obvious names! I'm now less sure about my answer (1) as that's more related to `operator=`, but I'll leave it up as it might inspire someone to add the correct answer. – Toby Speight Jul 15 '15 at 10:09
  • Probably the intent is that one could allocate memory, copy the object in and deallocate the memory, and that would be exactly equivalent to `new`, `operator=` and `delete`? – Toby Speight Jul 15 '15 at 10:11
  • @TobySpeight Thanks for your answer. Now suppose there was no f() at all, but Derived still adds a member (d). Base would now qualify as is_trivially_copyable, but memcpy-ing an array of Base&'s that actually references Derived's (is that what you had in mind?) would create as much havoc as if f() were present. I'm not sure how such a copy would even be possible syntactically - maybe you can give a toy example in code, of the scenrio you have in mind? – Ofek Shilon Jul 15 '15 at 10:29
  • @Ofek - No, if you `memcpy` a `Derived` without virtual methods, you'd just get the `Base` slice of the `Derived`. `d` wouldn't be copied, but nobody would get hurt, because there would never be any attempt to access it. It's only because of the possibility of accessing `Derived`'s members through a naively-copied `vtable` that makes it a problem. – Toby Speight Jul 15 '15 at 10:39
  • @TobySpeight: Now try to memcpy *an array* of 10 Derived's referenced via Base*, and watch as mayhem unfolds. As I understand it is_trivially_copyable is to be used as a test to memcpy a buffer of many T's - memcpy'ing them one by one seems pointless. – Ofek Shilon Jul 15 '15 at 10:44
  • @TobySpeight, another thought concerning condition (1): if a non-trivial destructor has to be called at the dest location, wouldn't a non-trivial assignment operator be mandatory anyway? (and so the type would be non trivially copyable without the extra condition?) – Ofek Shilon Jul 15 '15 at 10:48
  • 2
    *"you could `memcpy` from this variable to another Base object `w`"* This is not what [basic.types]p2 allows you to do. It specifically restricts the `memcpy` guarantee to complete objects. – dyp Jul 15 '15 at 11:18
  • @dyp Precisely. I found this answer when researching whether one can memcpy complete objects with nothing but some virtual function members. Do you know more? – Columbo Sep 14 '17 at 17:38