1

The rules of C++ say that it's legal and will work to copy an object or a POD type using memcpy.

They further say that a POD can't have a (non-trivial) destructor. Why is this and why would the mere addition of a destructor change the class in such a way that using memcpy would not work?

// Perfectly fine to copy using memcpy
struct data
{
    int something;
    float thing;
};

// Not allowed to copy using memcpy
int var;

struct data
{
    int something;
    float thing;
    ~data() { var = 1; }
};

Why would simply adding the destructor make it impossible to memcpy the struct's data? I can't imagine that this would require altering the data layout in any way.

I'm not interested in being told don't do this, I have no intention to do so... I understand I can't do this because "the standard says so" but I'm wondering what the reason the standard says so is as it doesn't seem a necessary restriction to me and want to understand the reasons.

EDIT People seem to be misunderstanding my question. I'm not asking if it's a good idea or not to use memcpy. I'm asking what is the reasoning behind making it illegal if there is a non-trivial destructor. I can't see what difference it makes and want to understand why this restriction exists. Most of the reasons I've been given about it being a bad idea apply just as much if there is a destructor or not.

jcoder
  • 29,554
  • 19
  • 87
  • 130
  • Usually the problem is not with destructor as such but with virtual functions, including virtual destructor. A class with a virtual function has a [invisible to the programmer] virtual functions table, which `memcpy` overwrites and the table entries (which are function pointers) end up having wrong values, i. e. pointing to the wrong addresses. – Violet Giraffe Sep 21 '16 at 10:58
  • I understand why virtual functions would break memcpy and the reason behind not being allowed to do that, but it doesn't seem to apply in principle to having a destructor just for having one. – jcoder Sep 21 '16 at 11:00
  • No, it does not apply here. What compiler do you use, on which operating system, and what specific fault do you observe when `memcpy`ing the class from your question? – Violet Giraffe Sep 21 '16 at 11:01
  • The reason PODs can't have a non-trivial destructor is because PODs can't have a non-trivial destructor. This is how C++ works. There is no further explanation. These are the rules of C++. Here are some similar questions: why is the sky blue; why is water wet; and why is grass green. – Sam Varshavchik Sep 21 '16 at 11:02
  • 3
    They didn't just go to the effort of making up the rule and documenting it for fun though. Presumably there is an actual reason why allowing this would break something, I don';t think it's unreasonable to ask the experts on here why the language would specify this restriction. – jcoder Sep 21 '16 at 11:05
  • I'd be more interested to hear why you think it should be legal to byte-wise blat a type that you've told the compiler to manage for you with nice high-level constructs like constructors and destructors. Can you not see how, in general, this would result in utter chaos? The language would no longer be able to make any guarantees about, well, any of those type's members or their lifetime. What's the point in that? – Lightness Races in Orbit Sep 21 '16 at 11:07
  • Yes, there are reasons for it. C++ is arguably the most complicated contemporary programming language. Explaining the reasons for even some of the trivial aspects of the language can easily fill a whole chapter in the book. A few reasons can be given, perhaps, but a complete answer, describing the myriad of reasons, is simply not feasible, on some silly web site called "stackoverflow.com". – Sam Varshavchik Sep 21 '16 at 11:08
  • @LightnessRacesinOrbit but all of those reasons are the same whether there is a destructor or not. – jcoder Sep 21 '16 at 11:11
  • 2
    @jcoder: I don't see how. Being allowed to blat things at the byte level is allowed, as a special case, for compatibility and for "extreme" whatevers, only for a class with no special member functions that you're saying "I will take complete charge of this". As soon as you add any class-like features, you're back to normal, "safe" C++, and blatting stuff violates safety rules because the outcome can no longer be specified by the language as soon as you do so. Seems reasonable to me. Remember, C++ is an abstraction, not a machine language. – Lightness Races in Orbit Sep 21 '16 at 11:13
  • This may not be entirely related but regarding the **why** a similar reasoning / motives for this decision you can see regarding "rule of 3/rule of 5" http://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11 – Hayt Sep 21 '16 at 11:36

6 Answers6

4

In layman's terms:

Why would simply adding the destructor make it impossible to memcpy the struct's data?

It doesn't make it impossible, just illegal.

I can't imagine that this would require altering the data layout in any way.

Probably won't, but it's allowed to. Because the class is no longer a POD (i.e. a c struct) it's now a c++ class.

Classes have different rules to PODs. Since we cannot predict how the compiler will go about coding them up, we can no longer reason about the outcome of memcpy.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • `a POD (i.e. a c struct)` Since C++11 POD's can have user defined constructors, so perhaps they are no longer necessarily fully C compatible. – eerorika Sep 21 '16 at 11:38
  • @user2079303 I think you'll find that a POD, even with a constructor, is layout-compatible with a c-struct. The addition of a constructor doesn't imply special handling, merely convenient initialisation (which we can already achieve in C with brace-initialisation) – Richard Hodges Sep 21 '16 at 11:43
  • @user2079303 I think since C++11 the term POD is no longer used. – juanchopanza Sep 21 '16 at 13:14
  • @juanchopanza [are you sure](http://eel.is/c++draft/class#10)? Richard: Sure, it's layout-compatible. If that's what you mean by POD being *c struct*, then I agree completely. – eerorika Sep 21 '16 at 13:17
  • @user2079303 OK, I should have said *refined*, by splitting into two separate concepts (*trivial* and *standard layout*.) – juanchopanza Sep 21 '16 at 13:20
1

Non-trivial destructors typically reverse some non-trivial action performed in a constructor (or other member functions) that affect the object state.

memcpy() copies the bits that make up the object. If the behavior of a constructor would give a different set of bits, then the destructor on that object will try to reverse some action that has not actually occurred.

A typical example is a class who's constructors allocate some resource (e.g. memory), other member functions ensure that resource remains in a sane state, and the destructor releases that resource. Copying such an object using memcpy() will copy the handle of that resource, rather than creating a new instance of that resource for the copied object [which is what the copy constructor of such an object typically does]. The destructor - eventually - will be invoked for both the original and copied objects, and the resource will be released twice. For memory (e.g. allocated using C's malloc() or C++'s operator new) releasing twice gives undefined behaviour. For other resources (file handles, mutexes, other system resources) the result varies, but - on must systems - it is generally inadvisable to deallocate a single something twice.

The other problem is that a class may have base classes, or have members, that themselves have non-trivial constructors and destructors. Even if the class itself has a constructor which does nothing, destroying an object invokes destructors of all members and bases. Copying such an object using memcpy() affects those base classes or members in the way I describe above.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • Ok that makes some sense. It doesn't really apply to my example though, but I guess the point is that it's easy to make mistakes – jcoder Sep 21 '16 at 11:45
  • Your example is contrived, so the destructor only affects a static variable in a way (i.e. setting constant value) that is practically unlikely to give a different result on usage of `memcpy()` to copy an instance of the class. The thing is, with less contrived examples (e.g. all constructors increment `var`, destructor decrements it, so `var` tracks the number of "live" objects), using `memcpy()` would produce invalid (negative) values of `var`. A compiler cannot be expected to make sense of the differences between your contrived example and similar examples where the difference matters – Peter Sep 21 '16 at 12:23
0

Only objects which are trivially copyable can be copied using memcpy. A class with non-trivial destructor is not trivially copyable.

Suppose for an example, your class has a pointer as one of its member. You allocate space for that pointer in the constructor of the class. Now, you can do a variety of things in the non-trivial destructor like deleting the space you allocated. By memcpy you'll be copying the entire structure bit by bit. So two instances will be trying to delete the same pointer when their destructors are called.

nishantsingh
  • 4,537
  • 5
  • 25
  • 51
  • Yes I understand that, what I'm asking is WHY the language specifies this, I presume there is a reason, they diudn't just decide that for fun – jcoder Sep 21 '16 at 10:59
  • This is not an answer. – Violet Giraffe Sep 21 '16 at 10:59
  • @jcoder WHY? But he tells you why, because it is dangerous! Of course, it can be possible, in some cases, that a non trivial class could be copyed with memcpy, but the compiler can't analyse what you are really doing and in general it is dangerous. – Jean-Baptiste Yunès Sep 21 '16 at 11:10
  • @Jean-BaptisteYunès The answer was different when the comment was made. (And I don't agree about the reasoning. The question is not about double-frees, it's about destructors (period)) – deviantfan Sep 21 '16 at 11:15
  • 1
    @deviantfan The question ultimately boils down to why non-trivial destructors impose such a restriction on a class being copyable. This is one of the possible reasons why the standard prohibits such a class to be non-copyable. Why is it so hard to agree upon? – nishantsingh Sep 21 '16 at 11:17
  • Just the existence of a destructor means something "special" is being done/could be done. Which makes c++ become pessimistic and disallows copying byte-by-byte. – Hayt Sep 21 '16 at 11:22
  • @user3286661 Because it still doesn't explain it. What if I want a descructor which prints "An object of class X was destroyed"? Prohibiting such things because some people can't do proper memory management is nonsense. Pretty much everyone here talks about memory and nobody about actual destructors. (Richard Hodges has an actual answer...) – deviantfan Sep 21 '16 at 11:25
  • @deviantfan Why can't people make copy constructors/assignment operators if they care so much about memory management ! – nishantsingh Sep 21 '16 at 11:28
  • @user3286661 I'm not sure what you want to say – deviantfan Sep 21 '16 at 11:29
  • @deviantfan I'm trying to say that make copy constructors/assignment operators. Why do you want to rely on memcpy? – nishantsingh Sep 21 '16 at 11:30
  • @deviantfan the answer explains what happens not why. The compilers do not "look" into the implementation of the destructor but just check for a declaration. Ofc a destructor can just print things, but it can also do much more else. C++ (since c++11) goes a more pessimistic approach and makes things which possibly cause strange errors illegal. – Hayt Sep 21 '16 at 11:31
0

The problem is usually that when you have a destructor which does something, you also should have a copy constructor/assignment operator (look up "Rule of 3" for this).

When you memcpy, you will skip these copy operation and this can have some consequences.

E.g. you have a pointer to an object and delete it in the constructor. You then should also specify a copy operation so you copy the pointer/object there too. If you use memcpy instead you have 2 pointers to the same instance and the second destruction would cause an error.

The compilers cannot know what you do in your destructors and if special behavior is needed so it's pessimistic and is seen as a non-POD type anymore. (even if you do nothing in the destructor).

A similar thing happens with the generation of move-assignment/move-constructors when you declare a destructor in a class in c++11.

Hayt
  • 5,210
  • 30
  • 37
0

This is because memcpy provides a shallow copy and if you have a non trivial dtor it is probably because your object is the owner of some resource, then a shallow copy would not provide you the right copy semantic (duplicate the ownership). Think about some structure with a pointer to something inside, the dtor should (probably) free the resource when the struct disappear, but a shallow copy will let you with a dangling pointer.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
-1

The problem arises when memory is owned by the class: you should not memcpy a class that owns memory (even if it has no destructor) take for instance:

https://ideone.com/46gFzw

#include <iostream>
#include <memory>
#include <cstring>

struct A
{
  std::unique_ptr<int> up_myInt;
  A(int val)
  :
    up_myInt(std::make_unique<int>(val))
  {}
};

int main()
{
    A a(1);
    {
      A b(2);
      memcpy(&a, &b, sizeof(A));
      std::cout << *a.up_myInt << std::endl;
      //b gets deleted, and the memory b.up_myInt points to is gone
    }
    std::cout << *a.up_myInt << std::endl;
    return 0;
}

which results in

stdout

2
0

stderr

*** Error in `./prog': double free or corruption (fasttop): 0x08433a20 ***

As b goes out of scope, the data it owned is deleted, a points to the same data, hence fun times (same happens if your class contains basically any other stl container so never ever memcpy an stl containter either.)

Lanting
  • 3,060
  • 12
  • 28