Skip to content

Add placement new operator #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 16, 2019
Merged

Add placement new operator #33

merged 3 commits into from
Sep 16, 2019

Conversation

Pharap
Copy link
Contributor

@Pharap Pharap commented Aug 18, 2018

This adds the missing placement new operator.
The placement new operator is especially useful when working with raw data buffers (often used when implementing data structures to avoid requiring a default constructor).

More information about placement new can be found here:
https://en.cppreference.com/w/cpp/language/new#Placement_new

Pharap added 2 commits August 18, 2018 09:09
Casting to void is a well known trick for prevening 'unused parameter' warnings.
@facchinm facchinm requested a review from cmaglie August 20, 2018 10:11
Copy link
Member

@cmaglie cmaglie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matthijskooijman
Copy link
Collaborator

Commit looks good to me. I believe there a few more things we could do to make support for new/delete more complete:

  • According to the C++ spec, the placement operators should be noexcept to enforce that they cannot throw exceptions. Since AVR is always compiled without exceptions, I believe this does not really matter, so the proposal is fine, I just wanted to mention this. Perhaps a comment could be added about this?
  • The C++ spec also mentions an array form of placement new (void* operator new[](std::size_t size, void* ptr) noexcept;), for completeness I would also add that.
  • The C++ spec also mentions placement delete functions, which is "called whecalled when any part of the initialization in a placement new expression". Since we're compiling without exceptions, I suspect these operators can never be called, so it is probably ok to leave them out. Perhaps adding a comment stating this could be added for future reference?

@Pharap
Copy link
Contributor Author

Pharap commented Sep 6, 2018

@matthijskooijman

According to the C++ spec, the placement operators should be noexcept

I don't have a copy of the standard to check with. I found an SO answer that claims §18.6 [support.dynamic] states that the new operator should be noexcept.

Originally I didn't think cppreference mentioned it in their listing of the contents of the <new> header, but I see now that it's mentioned under the 'Exceptions' header.

The C++ spec also mentions an array form of placement new (void* operator new[](std::size_t size, void* ptr) noexcept;), for completeness I would also add that.

That looks to be functionally similar so it should be trivial to add.


I'd be happy to add both the noexcept and the placement new[] version if everyone is in agreement that they should be added, and nobody can think of any reason why those changes shouldn't be made.


The C++ spec also mentions placement delete functions, which is "called whecalled when any part of the initialization in a placement new expression". Since we're compiling without exceptions, I suspect these operators can never be called, so it is probably ok to leave them out. Perhaps adding a comment stating this could be added for future reference?

You seem to have truncated the standard quote or at least are missing part of it.
I'm not sure what that quote is supposed to say.

I could add a comment discussing the operator and its absense, but as I don't have a copy of the standard to hand I'd need to know what exactly the standard says about its use to be sure that its absense is unimportant and to know the best way to explain its absence.

@matthijskooijman
Copy link
Collaborator

matthijskooijman commented Sep 6, 2018

I actually don't have the final spec at hand, but was quoting from a draft standard that is publically available. A quick search shows an even newer version here: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf

@Pharap
Copy link
Contributor Author

Pharap commented Sep 7, 2018

Ah, the relevant quote is:

Remarks: Default function called when any part of the initialization in a placement
new-expression that invokes the library’s non-array placement operator new terminates by throwing an exception.

As per §18.6.1.3.9, or Language support library > Dynamic memory management > Storage allocation and deallocation > Placement forms.

I believe this means that technically the default definition of placement new can't trigger this either.
Most likely it exists purely to account for cases where placement new is overloaded in a way that can throw exceptions.

The definition is given as void operator delete(void* ptr, void*) noexcept; and the effects section (§18.6.1.3.7) states "Effects: Intentionally performs no action".


We could implement both versions (array and non-array) with a comment above each stating "this function is expected to be unused" and a comment in the bodies stating "this function intentionally does nothing".
The function should go unused so they shouldn't add any overhead to any programs unless explicitly called somehow, in which case they will either be reduced to an empty statement or the cost of a single call per usage.

If it was left unimplemented, I'm not sure there's a particularly brief way to exaplain why it's unimplemented without leaving out information.
The tersest comment I can think of is:
"the placement new operators are supposed to have corresponding delete operators, but they have been deemed unneccessary because they should only be called if the placement new operators throw an exception, which is impossible in a typical Arduino environment".
But that's also a viable option. Perhaps someone else can think of a more succinct comment?

I am happy with either approach.

@matthijskooijman
Copy link
Collaborator

I believe this means that technically the default definition of placement new can't trigger this either.
Most likely it exists purely to account for cases where placement new is overloaded in a way that can throw exceptions.

My interpretation was that it would be called when the class' constructor would throw an exception, since that's what initialization means AFAIU (calling the placement new operator is allocation rather than initialization). I can't find hard evidence of this, but it makes sense that a call to placement new is coupled to a call to placement delete, and if placement new fails (e.g. throws), there is no need to do placement delete. This seems consistent with sections like 5.3.4.23 (which says the return value of placement new is passed to placement delete, in addition to the "placement arguments", e.g. placement_args in new (placement_args) Class(args), which is not necesarily limited to a single pointer argument, see 5.3.4.13).

The definition is given as void operator delete(void* ptr, void*) noexcept; and the effects section (§18.6.1.3.7) states "Effects: Intentionally performs no action".

I suspect this means the default intentionally performs no action, but if the default placement new provided by the compiler has any side effects, the placement delete allows undoing them.

For reference, here's what gcc does: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/new#L125 (see the new*.cpp files in the same directory for implementations). I see a few additional signatures there, like an aligned version (not in the spec AFAICS), but also a delete that takes a size argument and nothrow versions of everything, which are in the spec. I guess there is no additional value in those (the nothrow version could be useful for making sketches more portable with platforms that do use exceptions, but that would also require defining bits of the std namespace, which might be tricky when combined with libraries such as uclibc++ that also populate std). I can't find when the delete(void*, size_t) would be called according to the spec just now.

Given the extra versions, we'll either have to implement a ton more, or end up not implmenting everything anyway. Hence, I'd suggest we just leave the code as is, but add a comment:

The specification contains additional forms for operator new and delete, but these seem to be never called by the compiler. For simplicity, these are left out, though they might be added later if needed. In particular, there is:
 - Operator delete with a size argument, which should just call operator delete without a size, which is also what the compiler seems to do directly, so this is never used.
 - Versions that accept a std::nothrow argument to prevent throwing. Since we always run without exceptions, providing these does not add anything (except maybe for portable sketches?).
 - Placement delete operators, which are only to be called when placement new initalization fails with an exception, which is impossible.

It is not really consise, but does capture what we learned for future reference, I guess?

@facchinm facchinm added the c++1x label Oct 1, 2018
@Pharap
Copy link
Contributor Author

Pharap commented Oct 26, 2018

Sorry for the delay in replying to this, I have been preoccupied with other projects.


since that's what initialization means AFAIU (calling the placement new operator is allocation rather than initialization)

I think calling the placement new operator is initialisation, as the pointer passed to placement new should point to a block of bytes that have already been allocated (be it statically or dynamically).

Regardless of terminology, I've looked around and several places (e.g. Wikipedia, Stack Overflow) indicate placement delete is intended to be called when the constructor of an object in a placement new expression throws an exception.

I.e. In T * p = new (buffer) T(); (where new refers to void * operator new(std::size_t, void *) , if T's constructor throws an exception, void operator(void *, void *) will be called.

I can't find when the delete(void*, size_t) would be called according to the spec just now.

I think you're looking for delete(void *, void *).
delete(void *, std::size_t) would be called to react to new(std::size_t, std::size_t).

like an aligned version (not in the spec AFAICS)

If you mean void* operator new(std::size_t, std::align_val_t) then that's a C++17 addition, according to cppreference (overloads 3 and 4).
It's specified in §21.6 of the C++17, which is where §18.6 of the C++14 standard has moved to.


But I think for now the scope of this PR should be limited to just the 4 placement operators specified by "Placement forms", §18.6.1.3 of C++14 (of which N4296 is a draft), and to ignore any other overloads for now, such as const std::nothrow_t &)

I think it's better to keep the scope of this PR small, and to assess the usefulness of the other operators another day in another PR.

So that leaves us with four operators:

  • void * operator new(size_t, void *) noexcept
  • void * operator new[](size_t, void *) noexcept
  • void operator delete(void *, void *) noexcept
  • void operator delete[](void *, void *) noexcept

I shall add noexcept to the already implemented operator, but the question remains of what to do about the others?
I believe they should be added so that all four are implemented, and that no additional comments are required - the other variants of new and delete should be covered by another PR.

What does everyone else think?


Out of interest, I checked a C++17 draft, N4713 and found that the "Placement forms" section had been relocated to §21.6.2.3 and renamed "Non-allocating forms", but that the same 4 operators were defined, with just one amendment - the addition of a [[nodiscard]] attribute.

@matthijskooijman
Copy link
Collaborator

I shall add noexcept to the already implemented operator, but the question remains of what to do about the others?

Sounds ok to me.

I believe they should be added so that all four are implemented, and that no additional comments are required - the other variants of new and delete should be covered by another PR.

As mentioned, I do not see direct value in the placement delete forms, but they should not harm either (and for consistency, implementing them is probably a good idea).

A comment might still be useful for the other new operators, but I'm not really attached to that comment.

@cmaglie, @facchinm, any thoughts?

The standard mandates that placement new should be have a noexcept specifier.
@facchinm facchinm added this to the next milestone Oct 29, 2018
@aentinger aentinger merged commit 742abcd into arduino:master Sep 16, 2019
@Pharap Pharap deleted the add-placement-new branch September 16, 2019 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants