开发者

Examples of C++ classes not binary moveable

开发者 https://www.devze.com 2023-01-06 18:48 出处:网络
Does anyone know of any C++ class which are not binary moveable? In other words, do you know any class where this results in memory-leak or undefined behaviour?

Does anyone know of any C++ class which are not binary moveable?

In other words, do you know any class where this results in memory-leak or undefined behaviour?

{ // New scope
// Allocate some memory
unsigned char data1[sizeof(MyClass)];
unsigned char data2[sizeof(MyClass)];
// Create an instance of the class
::new (data1) MyClass();
// Create a pointer and run some class-specific code
MyClass* ptr = (MyClass*) data1;
ptr->MyFunction();
// Binary move everything to data2
::memcpy((void*)data2, (void*)data1, sizeof(MyClass));
::memset((void*)data1, sizeof(MyClass), 0); // for clarification
// Run some code at the new memory location, same object
ptr = (MyClass*) data2;
ptr->MyFunction();
// Run destructor at memory location2
(&*data2)->~MyClass(); 
} // ... out of scope. No implicit destructors called. No memory leaks.

Update:

  1. By binary moveable I mean a开发者_Python百科ny object which can be binary moved to another location in memory and still function properly (the move constructor could be implemented with memcpy()).

  2. Of course any object which has pointers pointing to them cant be moveable, that's not part of the question.

  3. (clarify) tr1::smart_ptr, std::deque, std::vector and std::list and like classes can be binary moveable so dont bother arguing about thier internal behaviour when reallocating nodes.

  4. (clarify) Classes handling memory allocation can be binary movable, as a binary move means you are not handling both objects after the move, and the destructor wont be called twice.


If any other object stores a pointer to your object your object is no longer binary moveable (also called bitwise moveable). A doubly-linked list element is a good example. When you want to relocate an element of a doubly-linked list you need to adjust pointers in the adjacent elements. So if you try to only use memmove() to relocate the element the list breaks.


A class using raw memory to avoid premature construction:

template <class T, size_t N>
class aligned_storage
{
public:
private:
  T* mHead;
  unsigned char mData[N*sizeof(T) + std::tr1::alignment_of<T>::value - 1];
};

Here the issue is two fold:

  • mHead points into the object itself, so it should be changed, of course
  • even if we instead used some kind of byte count for the offset, this offset would have to be changed after the move.

And of course, any class using some subscription mechanism (observer pattern), because you would need to unsubscribe from the copied object and subscribe from the new object.

There is not much difference between using bitwise copy in a Move Constructor than for a Copy Constructor... and it's a bad idea as soon as you're dealing with non-PODs.


Any stream class (*) will have an internal buffer, and a pointer pointing to the next character within that buffer. They would not be binary movable.

(*) Essence of a stream class: Provides access to some continuous stream of data, one portion (char, int, string etc) at a time. Almost always implemented by reading a large block from the source of the data (e.g. a file) at once, and then stepping through it with a pointer.


  • I use an offset_ptr type to build structures in memory blob, which I want to swap in and out from memory to disk or network, or use with shared memory. The offset_ptr stores the object location "t" as the distance between "this" and "t". In order to copy an offset pointer, the internal offset has to be adjusted, hence it can only be memmoved if the object it points to it memmoved by the same amount (which is the case for the aforemention memory blob).

  • It is not uncommon to use an objects location as a key to access an associated resource, in particular if the ressource is frequently unnused and would add considerable overhead if aggregated into the object itself. For example, if you want every object to have an optional mutex, you might want to make up a global hash table where you map the objects location (this) to it's associated mutex, which is of course created only when actually requested. You can't memmove such an object anymore, obviously.


Smart pointers, containers and other resource management classes, whose destructors are responsible for releasing resources, must be invalidated by the move. Otherwise, the old object will release the resource which should have been transferred to the new one.

UPDATE: this is an answer to the original question, which has since been changed to invalidate it.


Some classes that hold variable-length data have allocation functions that allocate sizeof(Class) + ExtraDataSize in a contiguous block, and then use placement new to create an object in that space. In such a scheme, there is no need for the class to have a member pointer to the extra data, as it will be located at (unsigned char*)this + sizeof(*this).

Obviously, such a class won't have a public copy ctor. In theory you could binary move it, but the allocator returns only a Class* and you won't know how much extra bytes you need to move.


What makes C++ object hard to move is not individual objects stopping to work (within themselves) when copied. That problem touches relatively small class of objects (like objects containing pointers to themselves or objects working closely in some kind of circular reference set).

C++ objects are hard to move because:

  1. They are pointed to! That's the number one problem. Unless you find some way to ensure that after moving all pointers to the object that might be used in the future will be updated, there is no point in thinking about moving objects at all. C++, by definition, makes it almost impossible.

  2. It's hard to determine actual class (and size) of any given object at runtime. How are you going to create any general moving procedure, if you cannot determine what class the object is (without it you cannot update embedded pointers if needed), where it really starts (think: virtual inheritance) and what size it is (say what you want, but good old C structures with variable sized buffer at the end are valid C++ objects; the same goes for C arrays).


Update (after being downvoted by OP for not answering his questions)

Exact answers to the exact questions:

  1. Does anyone know of any C++ class which are not binary moveable?

    Yes.

    Every single C++ class is not binary movable by definition presented in the question. That's simply logical.

    Instance of every C++ class can have pointers pointing to it. There is nothing the author of the class can do to prevent it. Moreover every class instance, at some point of time, certainly has a pointer pointing to it. So no C++ class can satisfy condition 2: any object which has pointers pointing to them cant be moveable. So no C++ class can be binary movable by this definition.

  2. In other words, do you know any class where this results in memory-leak or undefined behaviour? [snippet of source code follows]

    (Ignoring the fact that question 2 is not question 1 formulated "in other words". Not even close. Instead I'll answer question 2 as it is by itself.)

    Yes. Many.

    There were several examples already provided.

    More examples:

    • Linked list implemented as circular doubly linked list with sentinel node embedded as a member in linked list class object itself. Executing this code -- assuming that first ptr->MyFunction(); creates at least one node -- will result in undefined behavior when loop deleting nodes reaches location of old sentinel node (which is now zeroed).

    • Form (as in GUI element) containing some controls (with pointers to them as members) and listening to their events. If second ptr->MyFunction(); allow events to be triggered (eg. by executing some kind of Form::Show method), it will lead to undefined behavior when event handlers try to manipulate controls via pointers which have already been zeroed.

    • If Class has sizeof == 13 (for example, exact value is not important) and its first member requires alignment on 4 byte boundary (e.g. it's a virtual function table pointer) then it is an undefined behavior because alignment of data2 does not fit requirements of Class. On some platforms it doesn't matter. On some other it will simply run much slower. On others it will crash.


I think it's hard for you to know the exact size of your class' instances, especially taking virtual methods into account.

0

精彩评论

暂无评论...
验证码 换一张
取 消