C++'s std::string solves this. Not only does it take care of all memory management for you, it also stores the string as length and a pointer. There's also a common 'small string' optimisation that stores strings a few bytes long inside the class itself, so it isn't doing any dynamic allocation. O(1) to get the size and small strings don't allocate - nice! Still, people will continue to moan that C++ is rubbish or something. You can even invent safe stack-allocated strings using templates to mimic exactly how C does it but in a safe and straightforward way, but the idea hasn't caught on - seems std::string is just fine.
To be fair C++ is best described as using both, since string literals are still null-terminated, and in many cases you have to use the std::string::c_str() method for backwards compatibility where a null-terminated string is expected (which is often regularly, in practice).
I think that is the real reason people commonly complain about C++. It's stuck in some sort of weird twilight zone, halfway towards being a modern safe language, but retaining enough of C to make it dangerous. Arguably more dangerous than C since newcomers may not be aware of what they are getting themselves into.
Kind of like the difference between a pit of quicksand, and a pit of quicksand covered in palm leaves. ;)
I don't agree that it is more dangerous than pure C. First, you can do all your operations on strings in 100% C++, which will be aggressively optimized to essentially what you'd be doing with C anyway. Second, keeping the underlying representation as NUL-terminated allows you to use other APIs that consume char * C-style strings by calling c_str(). There is a compile-time const check that forces you to write a cast when calling APIs that do not consume a const char *. If the APIs that you are calling do not modify the string, but merely read it (which is the case for almost all uses), then you can have a C++ app that is completely safe from string buffer overflows.
This is called abstracting out an easy-to-make-tragic-mistakes-in problem into a small layer (C++ std::string) and using that layer everywhere.
To be fair C++ is best described as using both, since string literals are still null-terminated, and in many cases you have to use the std::string::c_str() method for backwards compatibility where a null-terminated string is expected (which is often regularly, in practice).