"as if" in language standards [duplicate]
Asked Answered
E

3

13

What is the exact meaning of the phrase "as if" in the standard and how does it work when a user can modify individual parts of the behavior.

The question is in regards to the C++ standard when talking about the nothrow version of operator new. 18.4.1.1/7 reads (my emphasis):

This nothrow version of operator new returns a pointer obtained as if acquired from the ordinary version.

My understanding is that "as if" does not require a specific implementation as long as the behavior is appropriate. So if operator new was implemented like this (I know this is not a compliant implementation as there is no loop or use of the new_handler; but I'm shortening that to focus on my issue):

// NOTE - not fully compliant - for illustration purposes only.
void *operator new(std::size_t s)
{
    void *p = malloc(s);
    if (p == 0)
        throw std::bad_alloc();
    return p;
}

Then it would be legal to write the nothrow version like this:

// NOTE - not fully compliant - for illustration purposes only.
void *operator new(std::size_t s, const std::nothrow_t &nt)
{
    return malloc(s);
}

But let's say a program replaces operator new to use some other allocator. Does "as if" mean the compiler has to automatically change the behavior of the nothrow version to use this other allocator? Is the developer required to replace both the plain and nothrow versions?

Emanuel answered 21/2, 2010 at 16:52 Comment(2)
gotw.ca/publications/mill15.htm gotw.ca/publications/mill16.htmToga
The latest draft of C++0x has changed wording: "This nothrow version of operator new returns a pointer obtained as if acquired from the (possibly replaced) ordinary version.". And the default behavior changed to "Calls operator new(size). If the call returns normally, returns the result of that call. Otherwise, returns a null pointer."Stringency
H
7

From 1.9 "Program execution:

conforming implementations are required to emulate (only) the observable behavior of the abstract machine

and in an informational footnote:

This provision is sometimes called the “as-if” rule, because an implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program. For instance, an actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no side effects affecting the observable behavior of the program are produced.

The standard does specifically note that the "as-if" requirement is binding on a replacement version of the nothrow version of operator new(). However, as I read it, that requirement would fall to the programmer overriding operator new() not the compiler. The flip side of this responsibility is that I think the standard pretty much requires the default implementation of the nothrow operator new() provided by the library must do something along the lines of calling the throwing new in a try/catch and return 0 if std::bad_alloc is caught.

Where the "as if rule" could come in to play here is if the compiler/linker/whatever were smart enough to figure out that when the default throwing new() was in being used, the default non-throwing new() could take the shortcut, but if the default throwing new() was overridden, the default non-throwing new() would have to act differently. I'm sure this is technically possible for an implementation (even if you probably can't express it in standard C++). I'd be surprised if there was ever an implementation that did this.

I might be reading too much into the requirement, but I think that's what can be inferred.

Herring answered 21/2, 2010 at 18:57 Comment(4)
I don't know why my first version of the answer talked about the required behavior of operator delete() since your question asked nothing about that. Too little sleep last night or something.Herring
about your comment starting with "the flip side ...", what else is in the standard that would require the default nothrow operator new to call the throwing operator new?Emanuel
I'm not sure there is anything else - what I was trying to get across is that I think this prevents the default nothrow new() from taking a shortcut an calling malloc() (or whatever the default throwing new() calls directly.Herring
I was reading it like it only reinforces that it binds also to the replacement version (since it doesn't say it only binds to the replacement version). In fact without that statement, it would bind to both the replacement and the default implementation. So maybe the only is somehow implied and it says thereby it doesn't apply to the default implementation (since otherwise the statement would be pretty much redundant). Not sure :)Stringency
F
2

If the change in allocator in operator new makes an observable difference in the behaviour of a compliant C++ program then yes, it might require a change in the implementation of the no-throw version. Specifically if operator delete expects only blocks allocated by the new allocator then the no-throw new must change.

My reading is that the use of as if allows an implementation such as yours when the user hasn't overriden the standard operator new. As soon as he has, the implementation must not use a malloc based no-throw operator new and must either call the user declared version explicitly or at least re-use enough of the user declared version that a conforming program cannot tell that this isn't how the no-throw version has been implemented.

Flitch answered 21/2, 2010 at 17:11 Comment(5)
If I understand correctly, you suggest it is the compiler's responsibility to ensure consistency between global operator new and global nothrow new when a user has only replaced one of them. That could be quite a burden for a compiler, especially if it has to "reuse enough of the user declared version". If that was the intent, perhaps the standard should have specified that global operator new is implemented in terms of nothrow new and cannot be replaced -- the user would only be allowed to override global nothrow new.Tysontyumen
the nothrow one just has to try { ... } the call to the ordinary operator new to prevent exceptions from escaping, i think. That however would mean that the ordinary new can't call the nothrow new. Weird. :)Stringency
@Dan: it's the other way around - the standard strongly suggests that nothrow new is implemented in terms of throwing new. I don't think there's any intent that it should be any other way, although if neither is replaced then there's the obvious optimisation of nothrow. Charles's "or at least" is only pointing out that maybe an implementation could wriggle out of this with some fancy footwork. The fact that this fancy footwork is very difficult isn't a flaw in the standard, since this is something that an implementation would probably never want to do. Just call the user's throwing new.Loesch
I did a test: void *operator new(size_t p) throw(bad_alloc) { printf("#"); return malloc(p); } int main() { printf("|"); delete new (nothrow) int; } . In fact, GCC does not print out any # after the last |. So it would not conform by our interpretation, since its as-if disregards important observable behavior.Stringency
Then I guess probably the standard means "the default nothrow new allocates as if by the default new", rather than "the default nothrow new allocates as if by the current new". Which is also fair enough.Loesch
T
0

The developer should replace both the plain and nothrow versions. Check out this article on GOTW.

My assumption is that the standard puts requirements on compiler (and runtime) default implementations. So the "as if" you quote is meant to inform the compiler vendor that its default implementations of those methods must meet the specified criteria. If a developer chooses to override only one version of operator new, I do not think it is the compiler's responsibility to make all other versions of operator new compliant. It is the developer's responsibility. But that's all my opinion, I don't have the spec handy at the moment to see what it says in the front matter.

Tysontyumen answered 21/2, 2010 at 17:22 Comment(2)
@Tysontyumen - the GOTW article is specifically talking about providing a complete set of replacements for class specific operator new. I believe the issue Herb Sutter is concerned about is that if you override class specific operator new, you'll hide nothrow operator new unless you also override it.Emanuel
Good point, I missed that you were asking about the global operators new. The last bullet-point before the summary may be relevant to your question: Sutter suggests that the global nothrow new ought to ensure consistent semantics with the normal new.Tysontyumen

© 2022 - 2024 — McMap. All rights reserved.