Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using placement new in generic programming

When using placement new in generic code to construct an object at a specified address, the usage pattern is a bit different from usual code. For example, consider this implementation of uninitialized_copy: ([uninitialized.copy])

template <class It, class For>
For uninitialized_copy(It first, It last, For dest)
{
    using T = typename std::iterator_traits<For>::value_type;
    for (; first != last; ++first, (void)++dest)
        ::new (static_cast<void*>(std::addressof(*dest))) T(*first);
}

This post addresses the following points from the perspective of the standard:

  • why ::new is used instead of just new;

  • why an explicit cast to void* is required.

like image 797
L. F. Avatar asked Aug 17 '19 19:08

L. F.


People also ask

In which cases we would need to use placement new?

Placement new allows you to construct an object in memory that's already allocated. You may want to do this for optimization when you need to construct multiple instances of an object, and it is faster not to re-allocate memory each time you need a new instance.

Why use placement new?

Placement new is used when you do not want operator new to allocate memory (you have pre-allocated it and you want to place the object there), but you do want the object to be constructed.

How does placement new work?

Placement new is a variation new operator in C++. Normal new operator does two things : (1) Allocates memory (2) Constructs an object in allocated memory. Placement new allows us to separate above two things. In placement new, we can pass a preallocated memory and construct an object in the passed memory.

Does vector use placement new?

With std::vector , a memory buffer of the appropriate size is allocated without any constructor calls. Then objects are constructed in place inside this buffer using "placement new".


1 Answers

(This answer uses N4659, the final C++17 draft.)

Why ::new is used instead of just new

::new ensures that the operator new is looked up in the global scope. In contrast, the plain new first looks up in the scope of the class if T is a class type (or array thereof), and only then falls back to the global scope. Per [expr.new]/9:

If the new-expression begins with a unary ​::​ operator, the allocation function's name is looked up in the global scope. Otherwise, if the allocated type is a class type T or array thereof, the allocation function's name is looked up in the scope of T. If this lookup fails to find the name, or if the allocated type is not a class type, the allocation function's name is looked up in the global scope.

For example, with

struct C {
    void* operator new(std::size_t, void* ptr) noexcept
    {
        std::cout << "Hello placement new!\n";
        return ptr;
    }
};

The plain new will cause this function to be found, thus printing unwanted message, whereas ::new will still find the global function and work properly.

The global operator new(std::size_t, void*) cannot be replaced because of [new.delete.placement]/1:

These functions are reserved; a C++ program may not define functions that displace the versions in the C++ standard library ([constraints]). The provisions of [basic.stc.dynamic] do not apply to these reserved placement forms of operator new and operator delete.

(See How should I write ISO C++ Standard conformant custom new and delete operators? for more about overloading operator new.)

Why an explicit cast to void* is required

Although the global operator new(std::size_t, void*) may not be replaced, new versions of ::operator new can be defined. For example, suppose that the following declaration is placed in the global scope:

void* operator new(std::size_t, int* ptr) noexcept
{
    std::cout << "Hello placement new!\n";
    return ptr;
}

Then ::new(ptr) T will use this version instead of the global version, where ptr is a int* value. The pointer is explicitly cast to void* to ensure that the void* version of operator new (which we intend to call) wins in overload resolution.


From comment:

But why do we want to call exactly global new for void* if some type has special overload of new for itself? Seems like normal overloaded operator is more suitable - why it's not?

Normally, new is used for allocation purposes. Allocation is something the user should have control over. The user can roll out more suitable versions for a normal new.

In this case, however, we don't want to allocate anything — all we want to do is create an object! The placement new is more of a "hack" — its presence is largely due to the lack of syntax available for constructing an object at a specified address. We don't want the user to be able to customize anything. The language itself, however, doesn't care about this hack, though — we have to treat it specially. Of course, if we have something like construct_at (which is coming in C++20), we will use it!

Also note that std::uninitialized_copy is intended for the simplest case where you just want to copy construct a sequence of objects in raw allocated space. The standard containers allow you to customize not only how the elements are allocated, but also how they are constructed, by means of an allocator. Therefore, they do not generally use std::uninitialized_copy for their elements — they call std::allocator_traits<Allocator>::construct. This feature is used by std::scoped_allocator_adaptor.

like image 110
L. F. Avatar answered Oct 02 '22 22:10

L. F.