If you think adopting the latest-and-greatest C++11/14/17 will guarantee your firm the latest technology, please follow me looking at how this sausage is made. We will be looking at gcc-7-20161204 and clang/libc++ 3.9 (stable) sources fresh from last month releases.
This one is a crowd favorite. to_string resides in libc++/src/string.cpp. Let’s assume T=int. The implementation calls
Performance warning: clang’s libc++ is running a loop, increasing the size of the string in the expectation that it does not need to allocate more memory.
Same thing with gcc-7/libstdc++-v3
and __to_xstring is defined in include/ext/string_conversions.h as
// Helper for the to_string / to_wstring functions.
__to_xstring(int (*__convf) (_CharT*, std::size_t, const _CharT*,
__builtin_va_list), std::size_t __n,
const _CharT* __fmt, ...)
// XXX Eventually the result should be constructed in-place in
// the __cxx11 string, likely with the help of internal hooks.
_CharT* __s = static_cast<_CharT*>(__builtin_alloca(sizeof(_CharT)
const int __len = __convf(__s, __n, __fmt, __args);
return _String(__s, __s + __len);
The big problem here is that gcc/libstdc++ is always allocating memory (exactly __n = 16 bytes) for the biggest string the conversion can yield. So even if you are printing “0” it will always allocate 16 characters. Not counting the time for std::string creation itself.
So BOTH implementations of std::to_string are calling vsnprintf() which has been part of the c library for what, 30 years already? I have to look it up, perhaps more.
The sausage: std::to_string() is just a wrapper, with an expensive hack job inside. Just avoid it.
std::chrono is one of the biggest stars of C++11. It promised to standardize all the clock mechanisms once and for all. Let’s look at the implementation? This is libstdc++-v3/src/c++11/chrono.cc line 53
// -EINVAL, -EFAULT
syscall(SYS_clock_gettime, CLOCK_REALTIME, &tp);
// EINVAL, EFAULT
std::time_t __sec = std::time(0);
Surprise! It is calling clock_gettime(), which has been in the libc since SUSv2, POSIX.1-2001. On clang 3.9.0/src/chrono.cpp it looks like a straight copy of it:
struct timespec tp;
if (0 != clock_gettime(CLOCK_REALTIME, &tp))
__throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed");
return time_point(seconds(tp.tv_sec) + microseconds(tp.tv_nsec / 1000));
#else // !CLOCK_REALTIME
return time_point(seconds(tv.tv_sec) + microseconds(tv.tv_usec));
#endif // CLOCK_REALTIME
Also, I have seen folks using and boosting std::high_resolution_clock as a new feature. A closer look reveals:
* @brief Highest-resolution clock
* This is the clock "with the shortest tick period." Alias to
* std::system_clock until higher-than-nanosecond definitions
* become feasible.
<span class="hljs-keyword">using</span> high_resolution_clock = system_clock;
What a disappointment.
So what about <random> that allegedly got rid of all the libc rand() calls? Here we see gcc is shamelessly opening /dev/urandom, a Linux kernel facility
random_device::_M_init(const std::string& token)
const char *fname = token.c_str();
if (token == "default")
fname = "/dev/urandom";
else if (token != "/dev/urandom" && token != "/dev/random")
_M_file = static_cast<void*>(std::fopen(fname, "rb"));
On clang 3.9.0/src/random.cpp we see basically the same thing
random_device::random_device(const string& __token)
: __f_(open(__token.c_str(), O_RDONLY))
if (__f_ < 0)
__throw_system_error(errno, ("random_device failed to open " + __token).c_str());
On libstdc++-v3 we can see on include/std/thread:
// Abstract base class for types that wrap arbitrary functors to be
// invoked in the new thread of execution.
virtual void _M_run() = 0;
using _State_ptr = unique_ptr;
typedef __gthread_t native_handle_type;
Then __gthread is defined in gcc-7/libgcc/gthr-posix.h:
typedef pthread_t __gthread_t;
typedef pthread_key_t __gthread_key_t;
typedef pthread_once_t __gthread_once_t;
Bingo! Here it is C++ leaning on its poor cousin. std::threads is in fact, a gift-wrapped pthreads which was defined in POSIX.1c, Threads extensions (IEEEStd 1003.1c-1995). We know gcc it is using gthr-posix.h because you can see in the command line:
$ gcc -v
Using built-in specs.
Configured with: ../gcc-6.2.0/configure --prefix=/mingw64 --with-local-prefix=/m ingw64/local --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_6 4-w64-mingw32 --with-native-system-header-dir=/mingw64/x86_64-w64-mingw32/includ e --libexecdir=/mingw64/lib --enable-bootstrap --with-arch=x86-64 --with-tune=ge neric --enable-languages=c,lto,c++,objc,obj-c++,fortran,ada --enable-shared --en able-static --enable-libatomic --enable-threads=posix --enable-graphite --enable -fully-dynamic-string --enable-libstdcxx-time=yes --disable-libstdcxx-pch --disa ble-libstdcxx-debug --disable-isl-version-check --enable-lto --enable-libgomp -- disable-multilib --enable-checking=release --disable-rpath --disable-win32-regis try --disable-nls --disable-werror --disable-symvers --with-libiconv --with-syst em-zlib --with-gmp=/mingw64 --with-mpfr=/mingw64 --with-mpc=/mingw64 --with-isl= /mingw64 --with-pkgversion='Rev2, Built by MSYS2 project' --with-bugurl=https:// sourceforge.net/projects/msys2 --with-gnu-as --with-gnu-ld
Thread model: posix
gcc version 6.2.0 (Rev2, Built by MSYS2 project)
On clang/libc++ the same story
typedef pthread_t __libcpp_thread_t;
int __libcpp_thread_create(__libcpp_thread_t* __t, void* (*__func)(void*), void* __arg)
return pthread_create(__t, 0, __func, __arg);
Again std::thread is pthreads, wrapped. Oh you C++, can’t you do anything by yourself?
No need to add, std::shared_mutex, std::shared_timed_mutex, shared_lock also are wrapping pthreads on posix platforms.
I got to give this one: both implementations are native C++. Well done.
Let’s talk about something that has not even made into the standard yet? Well coroutines are still a proposal but the C library has had it since 2001 with makecontext/setcontext.
Here I had a surprise: even though there is no mention of coroutines in both the libc++ or the stdlibc++, boost::context DOES have a very comprehensive implementation of the execution stack swap by Oliver Kowalke (see boost/libs/context/src/asm/ontop_xxx) where XXX is the platform eg x86_64_sysv_elf_gas. That is beautiful engineering, the type I would like to see all over the current C++ implementations.
However, I really doubt this will make into the standard the way it is on boost so I still stand correct.
I could go on for hours perusing both implementations of the standard library, even going into supc++ as well, showing that the entire “Modern C++” infrastructure is just wrappers around our old friend POSIX & WIN32 C-library.
Worse, usually these wrappers will impose significant performance penalties to your programs in order to stay consistent with the ISO C++ standard requirements. I strongly encourage you to glance over and have both the gcc/stdlibc++ and the libc++ source trees unzipped somewhere in your hard drive. The gcc/stdlibc++ because it is the de facto standard – it is even used by clang by default. The libc++, because its implementation is really 10x less cluttered than that of GCC (as the clang compiler sources are also much easier to read than gcc itself).
The libstdc++-v3 sources are found inside the GCC source tree, which can be downloaded from https://gcc.gnu.org/releases.html
The libc++ is provided as a module that can be inserted into the LLVM project tree. It is optional. It can be downloaded from http://libcxx.llvm.org/.
I want to show the new standards are all wrappers. Some lazy wrappers, some better implemented. I wanted to destroy the myth some MDs have that adherence to the latest standards is necessary for having the best technology onboard. The latest technology you will find on places like github, boost, the Linux kernel, vendor white papers and most importantly, in software as configurable hardware.