Error codes are simple and have no runtime overhead. They work well in C and in performance-critical code. But they require checking at every call site. Exceptions propagate automatically and can't be ignored.
They keep error handling separate from normal logic. But they add complexity to control flow. Modern C++ prefers exceptions for most code. Use error codes in hot paths where performance matters or when interfacing with C libraries.