Are the allocator-taking constructors of standard promise
/packaged_task
supposed to use the allocator for just the state object itself, or should this be guaranteed for all (internal) related objects?
[futures.promise]: "...allocate memory for the shared state"
[futures.task.members]: "...allocate memory needed to store the internal data structures"
In particular, are the below bugs or features?
*MSVC 2013.4, Boost 1.57, short_alloc.h by Howard Hinnant
Example 1
#define BOOST_THREAD_VERSION 4
#include <boost/thread/future.hpp>
#include "short_alloc.h"
#include <cstdio>
void *operator new( std::size_t s ) {
printf( "alloc %Iu\n", s );
return malloc( s );
}
void operator delete( void *p ) {
free( p );
}
int main() {
const int N = 1024;
arena< N > a;
short_alloc< int, N > al( a );
printf( "[promise]\n" );
auto p = boost::promise< int >( std::allocator_arg, al );
p.set_value( 123 );
printf( "[packaged_task]\n" );
auto q = boost::packaged_task< int() >( std::allocator_arg, al, [] { return 123; } );
q();
return 0;
}
Output:
...
[promise]
alloc 8
alloc 12
alloc 8
alloc 24
[packaged_task]
alloc 8
alloc 12
alloc 8
alloc 24
FWIW, the output with the default allocator is
...
[promise]
alloc 144
alloc 8
alloc 12
alloc 8
alloc 16
[packaged_task]
alloc 160
alloc 8
alloc 12
alloc 8
alloc 16
Example 2
AFAICT, MSVC's std::mutex
does an unavoidable heap allocation, and therefore, so does std::promise
which uses it. Is this a conformant behaviour?
N.B. there are a couple of issues with your code. In C++14 if you replace operator delete(void*)
then you must also replace operator delete(void*, std::size)t)
. You can use a feature-test macro to see if the compiler requires that:
void operator delete( void *p ) {
free( p );
}
#if __cpp_sized_deallocation
// Also define sized-deallocation function:
void operator delete( void *p, std::size_t ) {
free( p );
}
#endif
Secondly the correct printf format specifier for size_t
is zu
not u
, so you should be using %Izu
.
AFAICT, MSVC's
std::mutex
does an unavoidable heap allocation, and therefore, so doesstd::promise
which uses it. Is this a conformant behaviour?
It's certainly questionable whether std::mutex
should use dynamic allocation. Its constructor can't, because it must be constexpr
. It could delay the allocation until the first call to lock()
or try_lock()
but lock()
doesn't list failure to acquire resources as a valid error condition, and it means try_lock()
could fail to lock an uncontended mutex if it can't allocate the resources it needs. That's allowed, if you squint at it, but is not ideal.
But regarding your main question, as you quoted, the standard only says this for promise
:
The second constructor uses the allocator
a
to allocate memory for the shared state.
That doesn't say anything about other resources needed by the promise. It's reasonable to assume that any synchronization objects like mutexes are part of the shared state, not the promise, but that wording doesn't require that the allocator is used for memory the shared state's members require, only for the memory needed by the shared state itself.
For packaged_task
the wording is broader and implies that all internal state should use the allocator, although it could be argued that it means the allocator is used to obtain memory for the stored task and the shared state, but again that members of the shared state don't have to use the allocator.
In summary, I don't think the standard is 100% clear whether the MSVC implementation is allowed, but IMHO an implementation that does not need additional memory from malloc
or new
is better (and that's how the libstdc++ <future>
implementation works).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With