Proposal to add constructors to std::bad_alloc exception class.

250 views
Skip to first unread message

Rich Sposato

unread,
Sep 1, 2017, 3:28:44 PM9/1/17
to ISO C++ Standard - Future Proposals
Hi,

I wrote this proposal after discovering the std::bad_alloc exception class only has a default constructor. Other exception classes have constructors with a string parameter. The lack of a similar constructor in bad_alloc has caused problems for me.

This is the second draft of this proposal. Please provide feedback on how to improve it before I submit it.

I would especially prefer feedback on wording and style so that it conforms with the wording and style of accepted proposals. I would also like feedback on technical correctness.

If anybody has insights on upcoming changes to the standard exception classes that might affect this proposal, please let me know.

If you prefer a Word document version of this proposal, please let me know.

Thanks,

Rich Sposato

================

The bad_alloc Constructor Parameter Proposal

 

By Richard Sposato

September 2017

Contents

I.       Introduction. 1

II.      Technical Specifications. 1

III.    Motivation. 2

IV.    Impact on the Standard. 5

V.     Design Decisions. 5

VI.    Acknowledgements. 5

VII.   References. 5

 

 

I.       Introduction


A proposal to add two constructors to the std::bad_alloc class so it conforms to other exception classes.


Intended Audience:

These changes are intended for C++ programmers of intermediate through expert skill level who are either authors or users of memory allocation libraries.

 

II.     Technical Specifications

The current implementation of bad_alloc class supports only the default constructor. To avoid breaking existing code, this constructor will continue to exist, and will provide an implementation defined explanatory string for the what() function.


bad_alloc();


The two additional constructors will accept either a reference to const std::string or a pointer to a C-style string. These constructors will look identical to constructors in other exception classes.

explicit bad_alloc( const std::string & what_arg );

explicit bad_alloc( const char * what_arg );


An alternate constructor would use a string_view parameter instead of either of the two above constructors.

explicit bad_alloc( std::string_view what_arg );

 

Each will construct a bad_alloc exception object that stores what_arg as an explanatory string that can be accessed through the what() function.

 

III.  Motivation


There are several motivations for this proposal. Each of these will be described in detail.

1.      Conformance to existing exception classes.

2.      Ease of making templates that construct exceptions.

3.      Ease of deriving subclasses from exception classes.

4.      Changes to C++17 new and delete operators.

5.      Allowing allocators to inform callers exactly why allocations failed.

 

1.      Conformance to existing exception classes.


All other exception classes in the std namespace provide constructors that accept either a single parameter of either a reference to a const std::string or a pointer to a C-style string. The parameter provides a human-readable explanation for why the program threw an exception or actionable information allowing the calling code to correct the problem.

·         logic_error

·         invalid_argument

·         domain_error

·         length_error

·         out_of_range

·         runtime_error

·         range_error

·         overflow_error

·         underflow_error

·         tx_exception


Conformance to other exception classes will enable motivations two and three for this proposal.

 

2.      Ease of making templates that construct exceptions.

3.      Ease of deriving subclasses from exception classes.


Motivations #2 and #3 are related. They can be explained with a single example that uses both templates and inheritance to show why bad_alloc constructors should accept an explanatory string parameter.


Classes that derive from the standard exceptions cannot call the bad_alloc constructor using the same code they would to call other exception classes. This example illustrates how to wrap existing exception classes with a smart-exception class that derives from standard exceptions.


template < class ExceptionType >

class SmartException

{

public:

      SmartException( const char * message,

unsigned int line, const char * function ) :

            ExceptionType( message ),

            line_( line ),

            functionName_( function )

      {}

      // ... rest of class ...

private:

      unsigned int line_;

      const char * functionName_;

};


The SmartException constructor will call a subclass constructor that accepts a string parameter. This works for most exceptions, but not bad_alloc.


These lines define various smart exceptions using the above class and typedefs.


typedef SmartException< std::bad_alloc > SmartBadAllocException;

typedef SmartException< std::logic_error > SmartLogicErrorException;


This function shows how code would throw a smart exception so any code catching the exception can report exactly where the problem occurred.


void * Allocator::Allocate( std::size_t bytes, std::size_t alignment )

{

      void * place = FindPlace( bytes, alignment );

      if ( place == nullptr )

      {

            throw SmartBadAllocException( “Error! Unable to allocate bytes.”,

                  __LINE__, __FUNCTION__ );

      }

      return place;

}

 

4.      Changes to C++17 new and delete operators.


There are several additional new and delete operators in the C++17 standard. The additional operators all have a std::align_val_t parameter to support alignment-aware allocators. The std::align_val_t parameter will become part of class-specific new and delete operators along with the global operators.


void * operator new( std::size_t count, std::align_val_t al);

void * operator new[]( std::size_t count, std::align_val_t al);

void operator delete( void * ptr, std::size_t sz, std::align_val_t al );

void operator delete[]( void * ptr, std::size_t sz, std::align_val_t al );

 

Alignment-aware allocators would be unable to allocate the required number of bytes if the alignment is incorrect. (e.g. – The caller requests alignment on 16 byte boundaries, but the allocator only supports alignment on 4 or 8 byte boundaries.) If the allocator throws a bad_alloc exception for the invalid alignment, the caller may assume the program is out of memory instead of assuming the alignment is incorrect. Such a caller may call a new_handler believing it will make more memory available. A new_handler could spend an enormous amount of time looking for memory that is available only to find none, simply throw another bad_alloc exception, or terminate the program by calling std::abort or std::exit. None of these actions will resolve the simple problem of using the correct alignment.


A caller that is correctly informed the alignment is invalid could merely repeat the request with a different alignment value. This is a low cost action that provides the intended result – allocating a chunk of memory with the correct alignment.


This means that a bad_alloc exception with a valid explanatory string could provide the caller with actionable information to solve the problem, while a bad_alloc exception with no (or an inaccurate) explanatory string would lead the caller to perform an expensive or program-ending action.


When the std::align_val_t parameter is added to class-specific new and delete operators, users will be able to choose alignments that are not supported by the allocators they use. (This proposal assumes the global new and delete operators will support all alignment values in the std::align_val_t type, and thus it will be impossible to call a global memory operator with an invalid alignment value.)

 

5.      Allowing allocators to inform callers exactly why allocations failed.


Allocations may fail for reasons other than the program ran out memory. There are specialized allocators that only handle requests for specific sizes, such as pool allocators, that could throw exceptions if they receive requests for sizes they cannot handle.


This example shows how an allocator might throw a bad_alloc exception with actionable information.


void * PoolAllocator::Allocate( std::size_t bytes, std::align_val_t alignment )

{

      if ( ( bytes < minAllowedSize ) || ( maxAllowedSize < bytes ) )

      {

            throw SmartBadAllocException(

“Error! This allocator only handles allocations from 16 through 64 bytes in size.”, __LINE__, __FUNCTION__ );

      }

      if ( !IsValidAlignment( alignment ) )

      {

            throw SmartBadAllocException(

“Error! This allocator only handles alignments on 4 byte boundaries.”, __LINE__, __FUNCTION__ );

      }

      void * place = FindPlace( bytes, alignment );

      if ( place == nullptr )

      {

            throw SmartBadAlloc( “Error! Unable to allocate bytes.”,

                  __LINE__, __FUNCTION__ );

      }

      return place;

}

 

IV. Impact on the Standard

 

·         Implementing this proposal will have minimal impact on the C++ Standard.

·         It is a pure extension that does not break any existing code.

·         It does not require additional changes to any library or the existing language.

 

V.    Design Decisions


This proposal brings up various questions.


1.      Should the bad_alloc exception be used only allocators fail from lack of memory?

2.      Can it also be used for allocations that fail because of invalid parameters (e.g. wrong size or alignment)?

3.      Can it also be used for allocations that fail because of internal problems with the allocator?


One answer to the first and second questions is that allocators should throw an out_of_range or invalid_argument exception when the size or alignment parameter is invalid. Likewise, one could say allocators should throw logic_error or runtime_error for internal problems. These answers imply bad_alloc is reserved strictly for out of memory conditions, but not for other conditions that arise during allocations.


However, many programmers write code assuming that allocators only throw bad_alloc. They do not expect to catch logic_error or invalid_argument exceptions, so these exceptions will propagate past the functions that should have caught them. For that reason, I recommend using bad_alloc for any problems that arise during allocations.

 

VI. Acknowledgements


Many thanks to the following people who reviewed various drafts of this proposal and provided feedback.


1.      Herb Sutter

2.      Andrei Alexandrescu

 

VII.                       References

 

1.      http://en.cppreference.com/w/cpp/memory/new/bad_alloc

2.      http://en.cppreference.com/w/cpp/error/logic_error

3.      http://en.cppreference.com/w/cpp/memory/new/operator_new

4.      http://en.cppreference.com/w/cpp/memory/new/operator_delete

5.      http://en.cppreference.com/w/cpp/memory/new/set_new_handler

Ville Voutilainen

unread,
Sep 1, 2017, 3:35:05 PM9/1/17
to ISO C++ Standard - Future Proposals
On 1 September 2017 at 22:28, Rich Sposato <rich.s...@gmail.com> wrote:
> Hi,
>
> I wrote this proposal after discovering the std::bad_alloc exception class
> only has a default constructor. Other exception classes have constructors
> with a string parameter. The lack of a similar constructor in bad_alloc has
> caused problems for me.
>
> This is the second draft of this proposal. Please provide feedback on how to
> improve it before I submit it.
>
> I would especially prefer feedback on wording and style so that it conforms
> with the wording and style of accepted proposals. I would also like feedback
> on technical correctness.


I have a technical question. The proposed signatures look like
bad_alloc needs to store a copy of
the data passed as an argument for the constructor. How likely is
storing that copy to fail right
after an allocation failure?

I have a follow-up question: did you consider the alternative where
the caller of the new constructor must pass an rvalue reference
to a std::string, in which case there would be most likely a much
smaller allocation, as in the case of a dynamically
allocated string that doesn't fit into SSO, there would be none, and
in the SSO case, a very small allocation that
is not dynamic?

Another question I have is this: what sort of ABI impact will this
change have for existing standard library implementations?

Nevin Liber

unread,
Sep 1, 2017, 4:42:21 PM9/1/17
to std-pr...@isocpp.org
On Fri, Sep 1, 2017 at 2:35 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
I have a technical question. The proposed signatures look like
bad_alloc needs to store a copy of
the data passed as an argument for the constructor. How likely is
storing that copy to fail right
after an allocation failure?

You beat me to asking this question. :-)
 
I have a follow-up question: did you consider the alternative where
the caller of the new constructor must pass an rvalue reference
to a std::string, in which case there would be most likely a much
smaller allocation, as in the case of a dynamically
allocated string that doesn't fit into SSO, there would be none, and
in the SSO case, a very small allocation that
is not dynamic?

I don't think an rvalue reference to a std::string helps.  As was recently pointed out to me [exception]p2: Each standard library class T that derives from class exception shall have a publicly accessible copy constructor and a publicly accessible copy assignment operator that do not exit with an exception.

std::string doesn't meet that requirement (and cannot, since we effectively no long allow a reference counted std::string implementation as of C++11), so bad_alloc cannot hold one.

I suppose it could hold a shared_ptr<string>, but then we have to allocate the shared_ptr...

Another question I have is this: what sort of ABI impact will this
change have for existing standard library implementations?

+1 as well.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

Ville Voutilainen

unread,
Sep 1, 2017, 4:45:37 PM9/1/17
to ISO C++ Standard - Future Proposals
On 1 September 2017 at 23:42, Nevin Liber <ne...@eviloverlord.com> wrote:
>> I have a follow-up question: did you consider the alternative where
>> the caller of the new constructor must pass an rvalue reference
>> to a std::string, in which case there would be most likely a much
>> smaller allocation, as in the case of a dynamically
>> allocated string that doesn't fit into SSO, there would be none, and
>> in the SSO case, a very small allocation that
>> is not dynamic?
>
>
> I don't think an rvalue reference to a std::string helps. As was recently
> pointed out to me [exception]p2: Each standard library class T that derives
> from class exception shall have a publicly accessible copy constructor and a
> publicly accessible copy assignment operator that do not exit with an
> exception.
>
> std::string doesn't meet that requirement (and cannot, since we effectively
> no long allow a reference counted std::string implementation as of C++11),
> so bad_alloc cannot hold one.

Just make the copy operation terminate() if it fails to copy a string.
;) Flippant ideas
aside, this is a fairly important thing to consider in the scope of
this proposal, good
catc.. err, finding.

Thiago Macieira

unread,
Sep 1, 2017, 4:57:13 PM9/1/17
to std-pr...@isocpp.org
On Friday, 1 September 2017 12:28:44 PDT Rich Sposato wrote:
> Hi,
>
> I wrote this proposal after discovering the std::bad_alloc exception class
> <http://en.cppreference.com/w/cpp/memory/new/bad_alloc> only has a default
> constructor. Other exception classes have constructors with a string
> parameter. The lack of a similar constructor in bad_alloc has caused
> problems for me.
>
> This is the second draft of this proposal. Please provide feedback on how
> to improve it before I submit it.

This breaks binary compatibility by changing the size of bad_alloc. So there
needs to be a VERY good reason for this to happen.

So, what problems were those that you faced? What messages had you wanted to
include and couldn't? How would they be used?

> The two additional constructors will accept either a reference to const
> std::string or a pointer to a C-style string. These constructors will look
> identical to constructors in other exception classes.
>
> explicit bad_alloc( const std::string & what_arg );

This constructor has problems. If the string outlives the unwound scopes, then
you can call the constructor below. If it doesn't outlive, then it would mean
bad_alloc would need to allocate memory in response to failing to allocate
memory, which is nonsense.

> explicit bad_alloc( const char * what_arg );
>
>
> An alternate constructor would use a string_view parameter instead of
> either of the two above constructors.
>
> explicit bad_alloc( std::string_view what_arg );

This could work and be used in replacement of the const char * above.
Fortunately, strlen can't throw or cause further problems.

> *2. **Ease of making templates that construct exceptions.*
> *3. **Ease of deriving subclasses from exception classes.*
>
>
> Motivations #2 and #3 are related. They can be explained with a single
> example that uses both templates and inheritance to show why bad_alloc
> constructors should accept an explanatory string parameter.

You need to explain why you're deriing from bad_alloc in the first place.

> template < class ExceptionType >
> class SmartException
> {
> public:
> SmartException( const char * message,
> unsigned int line, const char * function ) :
>
> *ExceptionType( message ),*
> line_( line ),
> functionName_( function )
> {}
>
> // ... rest of class ...
> private:
> unsigned int line_;
> const char * functionName_;
> };
>
> The SmartException constructor will call a subclass constructor that
> accepts a string parameter. This works for most exceptions, but not
> bad_alloc.

Can't you specialise it as a member template with enable_if to take exactly
the same arguments of the class that you're deriving from?

> *4. **Changes to C++17 new and delete operators.*
>
> There are several additional new and delete operators in the C++17
> standard. The additional operators all have a std::align_val_t parameter to
> support alignment-aware allocators. The std::align_val_t parameter will
> become part of class-specific new and delete operators along with the
> global operators.
>
>
> void * operator new( std::size_t count, std::align_val_t al);
>
> void * operator new[]( std::size_t count, std::align_val_t al);
>
> void operator delete( void * ptr, std::size_t sz, std::align_val_t al );
>
> void operator delete[]( void * ptr, std::size_t sz, std::align_val_t al );
>
>
>
> Alignment-aware allocators would be unable to allocate the required number
> of bytes if the alignment is incorrect. (e.g. – The caller requests
> alignment on 16 byte boundaries, but the allocator only supports alignment
> on 4 or 8 byte boundaries.) If the allocator throws a bad_alloc exception
> for the invalid alignment, the caller may assume the program is out of
> memory instead of assuming the alignment is incorrect.

Maybe it should throw something different, like bad_alignment_argument (we
have bad_array_new_length). Though, to be honest, I wonder if it should throw
at all or if it should just abort execution. This is not a runtime condition,
this is a programming error.

> Such a caller may
> call a new_handler believing it will make more memory available. A
> new_handler could spend an enormous amount of time looking for memory that
> is available only to find none, simply throw another bad_alloc exception,
> or terminate the program by calling std::abort or std::exit. None of these
> actions will resolve the simple problem of using the correct alignment.

Agreed. That leads to the conclusion that throwing std::bad_alloc or calling
new_handler in response to wrong alignment values is the wrong action to take.

> This means that a bad_alloc exception with a valid explanatory string could
> provide the caller with actionable information to solve the problem, while
> a bad_alloc exception with no (or an inaccurate) explanatory string would
> lead the caller to perform an expensive or program-ending action.

The string is the worst way to report this problem. Why would an exception
handler do a strcmp on what()? In fact, *what* would it strcmp to? If you're
going to distinguish, do it on the typeid, which is what catch() does.

> *5. **Allowing allocators to inform callers exactly why allocations
> failed.*
>
>
> Allocations may fail for reasons other than the program ran out memory.
> There are specialized allocators that only handle requests for specific
> sizes, such as pool allocators, that could throw exceptions if they receive
> requests for sizes they cannot handle.

Same as alignment issue: the type of the thrown exception should be different.

> 1. Should the bad_alloc exception be used only allocators fail from
> lack of memory?

IMHO, yes.

> 2. Can it also be used for allocations that fail because of invalid
> parameters (e.g. wrong size or alignment)?

No.

> 3. Can it also be used for allocations that fail because of internal
> problems with the allocator?

No.

> However, many programmers write code assuming that allocators only throw
> bad_alloc. They do not expect to catch logic_error or invalid_argument
> exceptions, so these exceptions will propagate past the functions that
> should have caught them. For that reason, I recommend using bad_alloc for
> any problems that arise during allocations.

And well they shouldn't. As I said, those errors indicate programming errors,
not runtime conditions that can be handled.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Ville Voutilainen

unread,
Sep 1, 2017, 5:02:17 PM9/1/17
to ISO C++ Standard - Future Proposals
On 1 September 2017 at 23:57, Thiago Macieira <thi...@macieira.org> wrote:
> On Friday, 1 September 2017 12:28:44 PDT Rich Sposato wrote:
>> Hi,
>>
>> I wrote this proposal after discovering the std::bad_alloc exception class
>> <http://en.cppreference.com/w/cpp/memory/new/bad_alloc> only has a default
>> constructor. Other exception classes have constructors with a string
>> parameter. The lack of a similar constructor in bad_alloc has caused
>> problems for me.
>>
>> This is the second draft of this proposal. Please provide feedback on how
>> to improve it before I submit it.
>
> This breaks binary compatibility by changing the size of bad_alloc. So there
> needs to be a VERY good reason for this to happen.

That means that at least one implementation vendor will oppose it (not
automatically just because,
but because that VERY good reason needs to be so strong that the
vendor goes "oh yeah
that's a bad bug that should've been fixed ages ago", and that's not
the case here), and that
at least two NBs will comment on it and that one of them will oppose
it before it can land.
This proposal is now fighting an uphill battle.

Why can't the problem be solved by a new exception type?

Arthur O'Dwyer

unread,
Sep 1, 2017, 6:49:44 PM9/1/17
to ISO C++ Standard - Future Proposals
On Friday, September 1, 2017 at 12:28:44 PM UTC-7, Rich Sposato wrote:
Hi,

I wrote this proposal after discovering the std::bad_alloc exception class only has a default constructor. Other exception classes have constructors with a string parameter. The lack of a similar constructor in bad_alloc has caused problems for me.

If you're going to claim that it "caused problems", you should mention some of those problems, so that people can suggest alternative fixes.
My initial very strong impression is that you have an XY problem.

If you want to throw something that catches-like std::bad_alloc but also includes some additional member data... well, that's what inheritance is for.

class SmartBadAllocException : public std::bad_alloc {
    std::string what_;
public:
    SmartBadAllocException(const char *what) : what_(what) {}
    const char *what() const override { return what_.c_str(); }
};

Does this solve the problem you had?


1.      Should the bad_alloc exception be used only allocators fail from lack of memory?

2.      Can it also be used for allocations that fail because of invalid parameters (e.g. wrong size or alignment)?

3.      Can it also be used for allocations that fail because of internal problems with the allocator?


1. No, because (2).
2. Yes. I do this, for the reasons you stated below.
3. Yes, but "internal problems" suggests that an invariant failed, so I'd rather throw AssertionFailure or just go undefined-behavior. Throwing bad_alloc when an invariant has failed is probably going to hide the problem, whereas the debugging human would really rather see it flagged loudly.

One answer to the first and second questions is that allocators should throw an out_of_range or invalid_argument exception when the size or alignment parameter is invalid. Likewise, one could say allocators should throw logic_error or runtime_error for internal problems. These answers imply bad_alloc is reserved strictly for out of memory conditions, but not for other conditions that arise during allocations.


However, many programmers write code assuming that allocators only throw bad_alloc. They do not expect to catch logic_error or invalid_argument exceptions, so these exceptions will propagate past the functions that should have caught them. For that reason, I recommend using bad_alloc for any problems that arise during allocations.


I agree.

Pet peeve: The C++17 allocators such as std::pmr::monotonic_buffer_resource do not throw anything on invalid/too-aligned alignment; instead, they quietly return less-aligned pointers. If you're using one of these allocators, you'd better be checking every allocation to make sure the allocator didn't quietly give you a misaligned pointer! :P

–Arthur

Nicol Bolas

unread,
Sep 1, 2017, 7:04:17 PM9/1/17
to ISO C++ Standard - Future Proposals
On Friday, September 1, 2017 at 3:28:44 PM UTC-4, Rich Sposato wrote:

III.  Motivation


There are several motivations for this proposal. Each of these will be described in detail.

1.      Conformance to existing exception classes.

2.      Ease of making templates that construct exceptions.

3.      Ease of deriving subclasses from exception classes.

4.      Changes to C++17 new and delete operators.

5.      Allowing allocators to inform callers exactly why allocations failed.


Overall, I find your proposal poorly motivated. See below for details. But this proposal needs better motivation for its existence.
 

 1.      Conformance to existing exception classes.


All other exception classes in the std namespace provide constructors that accept either a single parameter of either a reference to a const std::string or a pointer to a C-style string. The parameter provides a human-readable explanation for why the program threw an exception or actionable information allowing the calling code to correct the problem.

·         logic_error

·         invalid_argument

·         domain_error

·         length_error

·         out_of_range

·         runtime_error

·         range_error

·         overflow_error

·         underflow_error

·         tx_exception


Conformance to other exception classes will enable motivations two and three for this proposal.


As others have pointed out, `std::bad_alloc` is special because it is due to exactly the kind of failure that your API simply cannot handle: the ability to allocate memory.

For the rest of my examinations, I will assume that the API is such that it takes a bare `const char*`, and passes it on directly, with the expectation being that you will ensure that the memory being pointed to will live to the catch site. Because without that, I'm fairly sure your proposal is essentially DOA.

2.      Ease of making templates that construct exceptions.

3.      Ease of deriving subclasses from exception classes.


Motivations #2 and #3 are related.

They can be explained with a single example that uses both templates and inheritance to show why bad_alloc constructors should accept an explanatory string parameter.


Classes that derive from the standard exceptions cannot call the bad_alloc constructor using the same code they would to call other exception classes. This example illustrates how to wrap existing exception classes with a smart-exception class that derives from standard exceptions.


template < class ExceptionType >

class SmartException

{

public:

      SmartException( const char * message,

unsigned int line, const char * function ) :

            ExceptionType( message ),

            line_( line ),

            functionName_( function )

      {}

      // ... rest of class ...

private:

      unsigned int line_;

      const char * functionName_;

};


The SmartException constructor will call a subclass constructor that accepts a string parameter. This works for most exceptions, but not bad_alloc.


Here's a much better version, one that can work with any exception class type, not just the ones that take a string:

template<class Exception>
class LocalizedException : Exception
{
 
template<typename ...Args>
 
LocalizedException(unsigned int line, const char * function, Args &&args)

   
: Exception(std::forward<Args>(args)...)
   
, line_(line)
   
, function_(function)

 
{}

};

Now, isn't that much better? You're not forcing yourself into this erroneous "all exceptions take strings when constructed" assumption. You can work with other types, those that have their own messages or whatever.

4.      Changes to C++17 new and delete operators.


There are several additional new and delete operators in the C++17 standard. The additional operators all have a std::align_val_t parameter to support alignment-aware allocators. The std::align_val_t parameter will become part of class-specific new and delete operators along with the global operators.


void * operator new( std::size_t count, std::align_val_t al);

void * operator new[]( std::size_t count, std::align_val_t al);

void operator delete( void * ptr, std::size_t sz, std::align_val_t al );

void operator delete[]( void * ptr, std::size_t sz, std::align_val_t al );

 

Alignment-aware allocators would be unable to allocate the required number of bytes if the alignment is incorrect. (e.g. – The caller requests alignment on 16 byte boundaries, but the allocator only supports alignment on 4 or 8 byte boundaries.) If the allocator throws a bad_alloc exception for the invalid alignment, the caller may assume the program is out of memory instead of assuming the alignment is incorrect. Such a caller may call a new_handler believing it will make more memory available. A new_handler could spend an enormous amount of time looking for memory that is available only to find none, simply throw another bad_alloc exception, or terminate the program by calling std::abort or std::exit. None of these actions will resolve the simple problem of using the correct alignment.


That sounds like a good reason to separate `bad_alloc` from `bad_alignment`. What we don't want is for users to try to do this:

catch(std::bad_alloc &e)
{
 
if(e.what() == ...)
 
{
   
//Handle bad alignment
 
}
 
else
 
{
   
//Handle OOM.
 
}
}

That's two catch statements macarading as one. That's bad coding.
 

5.      Allowing allocators to inform callers exactly why allocations failed.


Allocations may fail for reasons other than the program ran out memory. There are specialized allocators that only handle requests for specific sizes, such as pool allocators, that could throw exceptions if they receive requests for sizes they cannot handle.


This example shows how an allocator might throw a bad_alloc exception with actionable information.


void * PoolAllocator::Allocate( std::size_t bytes, std::align_val_t alignment )

{

      if ( ( bytes < minAllowedSize ) || ( maxAllowedSize < bytes ) )

      {

            throw SmartBadAllocException(

“Error! This allocator only handles allocations from 16 through 64 bytes in size.”, __LINE__, __FUNCTION__ );

      }

      if ( !IsValidAlignment( alignment ) )

      {

            throw SmartBadAllocException(

“Error! This allocator only handles alignments on 4 byte boundaries.”, __LINE__, __FUNCTION__ );

      }

      void * place = FindPlace( bytes, alignment );

      if ( place == nullptr )

      {

            throw SmartBadAlloc( “Error! Unable to allocate bytes.”,

                  __LINE__, __FUNCTION__ );

      }

      return place;

}


Unless you write an English language parser to figure out what those mean, the only "actionable" thing that can be done is by the programmer (aka: spitting the message out to the log), not by the program itself.

The only way to make this detectable by the program itself is to have it throw something other than (or more than) `bad_alloc`. None of which requires playing games with its string.
 

Rich Sposato

unread,
Sep 1, 2017, 8:37:49 PM9/1/17
to ISO C++ Standard - Future Proposals
Hi Everyone,

Thanks for your feedback and explaining why bad_alloc only has a default constructor, unlike the other exception classes.

I can discard this proposal and implement one of the solutions suggested.

Cheers,

Rich

Bo Persson

unread,
Sep 2, 2017, 6:15:47 AM9/2/17
to std-pr...@isocpp.org
On 2017-09-02 01:04, Nicol Bolas wrote:
> On Friday, September 1, 2017 at 3:28:44 PM UTC-4, Rich Sposato wrote:

>
> *5.**Allowing allocators to inform callers exactly why allocations
> failed.*
>
>
> Allocations may fail for reasons other than the program ran out
> memory. There are specialized allocators that only handle requests
> for specific sizes, such as pool allocators, that could throw
> exceptions if they receive requests for sizes they cannot handle.
>
>
> This example shows how an allocator might throw a bad_alloc
> exception with actionable information.
>
>
> void * PoolAllocator::Allocate( std::size_t bytes, std::align_val_t
> alignment )
>
> {
>
> if ( ( bytes < minAllowedSize ) || ( maxAllowedSize < bytes ) )
>
> {
>
> throw *SmartBadAllocException(*
>
> *“Error! This allocator only handles allocations from 16 through 64
> bytes in size.”, __LINE__, __FUNCTION__ );*
>
> }
>
> if ( !IsValidAlignment( alignment ) )
>
> {
>
> throw *SmartBadAllocException(*
>
> *“Error! This allocator only handles alignments on 4 byte
> boundaries.”, __LINE__, __FUNCTION__ );*
>
> }
>
> void * place = FindPlace( bytes, alignment );
>
> if ( place == nullptr )
>
> {
>
> throw SmartBadAlloc( “Error! Unable to allocate bytes.”,
>
> __LINE__, __FUNCTION__ );
>
> }
>
> return place;
>
> }
>
>
> Unless you write an English language parser to figure out what those
> mean, the only "actionable" thing that can be done is by the
> /programmer/ (aka: spitting the message out to the log), not by the
> program itself.
>
> The only way to make this detectable by the program itself is to have it
> throw something other than (or more than) `bad_alloc`. None of which
> requires playing games with its string.

Instead of parsing strings to identify a set of known reasons, we could
just as well have an enum failure_reason with those reasons specified.

An enum value is a lot easier to copy than a string. :-)

And now we are very close the the feastures already available in the
header <system_error>.



Reply all
Reply to author
Forward
0 new messages