简体   繁体   中英

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.

(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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM