shouldn't both behavior be the same either both( accessing by index and at()) give abnormal termination or exit normally?
No, they should not have the same behaviour. The behaviour is different intentionally. If it wasn't, then there would only be a need for one of them to exist.
The at
member function performs bounds checks. Any access outside the bounds of the container results in an exception. This is the same as the at
member function of std::array
or std::vector
for example. Note that an uncaught throw will cause the program to be terminated.
The subscript operator does not perform any out of bounds checks. Prior to C++11, any access to elements at indices > size()
has undefined behaviour. Under no circumstance is the subscript operator guaranteed to throw an exception. This is the same as subscript operator of an array, std::array
or std::vector
for example.
Since C++11, the behaviour of the subscript operator of std::string
was changed such that reading the element at index == size()
(i.e. one past the last element) is well defined, and returns a null terminator. Only modifying the object through the returned reference has undefined behaviour. Reading other indices outside the bounds still has undefined behaviour.
I do not know for a fact the rationale for not making corresponding change to at
to allow access to the null terminator, but I suspect that it was considered to be a backwards incompatible change. Making UB well defined is always backwards compatible, while ceasing exception throwing is not. Another possible reason is that it would have opened a route to UB (if the null terminator is modified), and the design of at
is to keep it free from UB.