*" here void moveItemDuringRehash(Item* " /> *" here void moveItemDuringRehash(Item* " /> *" here void moveItemDuringRehash(Item* "/>

C++ new operator with std::move to "copy" pointer

249 Views Asked by At

I recently find a code snippet as follows:

  // To be specific: the "Item" can be viewed as "std::pair<xxx, xxx>*" here
  void moveItemDuringRehash(Item* itemAddr, Item& src) {
    // This is basically *itemAddr = src; src = nullptr, but allowing
    // for fancy pointers.
    // TODO(T31574848): clean up assume-s used to optimize placement new
    assume(itemAddr != nullptr);
    new (itemAddr) Item{std::move(src)};
    src = nullptr;
    src.~Item();
  }

The code is originated from the Folly Lib of Facebook. The functionality of this code is simple: copy std::pair* referenced by src to the memory pointed by itemAddr.

The implementation should be very simple, as mentioned in the comment. But actually, the code does not. The new operator with std::move is confusing, and I am not sure what is happening under the hood. I guess. Item{std::move(src)} construct a temp object with move ctor of std::pair*. And the temp object is copy to the object pointed by itemAddr by copy ctor of std::pair*. I am not sure if my guess is correct. Thank you for sharing your opinion. By the way, I was wondering if there is any performance benefit from this new operator with std::move.

Another question is why src.~Item() is needed? For safety, I need to set src (std::pair*) to nullptr. But why I need to use src.~Item() to dtor a nullptr?

1

There are 1 best solutions below

4
user17732522 On

My guess without much context:


The function implements a destructive move. It not only moves the value of the Item, but also destroys the Item object.

Similarly, it doesn't expect that there is already a Item object at the location itemAddr. Instead it constructs one.

For a scalar type like std::pair<xxx, xxx>*, there isn't really any difference between a destructive and a non-destructive move, but the function is written so that Item may also be a more complex type than a raw pointer. Types that are not actually raw pointers, but behave like them and can be used to replace raw pointers in allocators are called fancy pointers. The function is written in such a way that general allocators can be supported, including those with fancy pointers. (This isn't obvious from your shown code where no allocator type is mentioned, but that's how it looks in context of the Folly sources.)

A fancy pointer might have non-trivial construction and destruction, so that the destructive move operation and construction of a new object may behave differently than simple assignment.

new (itemAddr) Item{std::move(src)}; is a placement-new to construct a new Item object at the location itemAddr, move-constructed form src. Move-construction is a non-destructive move, so to destruct the source object that was moved from, src.~Item(); is still required.

I am not really sure why src = nullptr; is there. The user of the function may not access src after the destructor call either way. Since C++20 this is also true for the pseudo-destructor call of scalar types. For a fancy pointer the assignment may have side effects, but I don't see why that would matter here. The only reason I could think of is that this is used defensively.

There are no temporary objects involved here.


Please note that I am not familiar with the context in the library. This is just a guess based on what you showed and a quick look at the source file.