积累一些能用得到的C++开发小技巧。
错误码/状态码在项目很常见,用于提示错误类型、状态,通常还会附带一些相关描述。通常错误码是统一管理的,例如使用宏或者枚举定义。
namespace statis_code {
enum ServiceCode {
kSuccess = 0,
// client exccept
kBadParams = 1000,
kIllegalAuth = 1001,
// service except
kServerExcept = 2001
};
const std::unordered_map<ServiceCode, std::string> kCodeMsg{
{kSuccess, "ok"},
{kBadParams, "except request params,check uri"},
{kIllegalAuth, "auth failed"},
{kServerExcept, "service unavailable"}};
std::string GetMsg(ServiceCode code) {
auto itr = kCodeMsg.find(code);
return itr == kCodeMsg.end() ? "unknown status" : itr->second;
}
} // namespace statis_code
上述做有几个不太好的地方,首先是错误码定义较为随意,有可能和系统错误码冲突.linux自身0-132有自己的定义;其次我们需要自己去保证错误码的不重复,当错误码较多且多人开发时可能会有冲突,但是这种冲突很难发现;用户可能还会继续拓展错误码,这时需要修改枚举和map。所以我们更希望: 错误码能在编译层面保证不重复;错误码定义希望是注册式的,提供给用户注册接口和获取msg接口。
无意间看到brpc定义错误码时使用了模板特例化保证不重复,同时也是注册式的写法。
template <int status_code> class ServiceCodeHelper {};
template <> class ServiceCodeHelper<statis_code::ServiceCode::kBadParams> {};
template <> class ServiceCodeHelper<statis_code::ServiceCode::kBadParams> {}; // 报错,重复定义
通过模板特例化,可能在编译期间发现重复定义的错误码。那么接下来做法上第一种做法类似,注册错误码描述即可。下面是注册函数:
errno_desc
数组存储对错误码的描述,在注册时先做范围检测、重复描述检测、是否系统错误码检测,然后将描述写入数组。
const int ERRNO_BEGIN = -32768;
const int ERRNO_END = 32768;
static const char* errno_desc[ERRNO_END - ERRNO_BEGIN] = {};
static pthread_mutex_t modify_desc_mutex = PTHREAD_MUTEX_INITIALIZER;
const size_t ERROR_BUFSIZE = 64;
__thread char tls_error_buf[ERROR_BUFSIZE];
int DescribeCustomizedErrno(
int error_code, const char* error_name, const char* description) {
BAIDU_SCOPED_LOCK(modify_desc_mutex);
if (error_code < ERRNO_BEGIN || error_code >= ERRNO_END) {
// error() is a non-portable GNU extension that should not be used.
fprintf(stderr, "Fail to define %s(%d) which is out of range, abort.",
error_name, error_code);
_exit(1);
}
const char* desc = errno_desc[error_code - ERRNO_BEGIN];
if (desc) {
if (strcmp(desc, description) == 0) {
fprintf(stderr, "WARNING: Detected shared library loading\n");
return -1;
}
} else {
#if defined(OS_MACOSX)
const int rc = strerror_r(error_code, tls_error_buf, ERROR_BUFSIZE);
if (rc != EINVAL)
#else
desc = strerror_r(error_code, tls_error_buf, ERROR_BUFSIZE);
if (desc && strncmp(desc, "Unknown error", 13) != 0)
#endif
{
fprintf(stderr, "WARNING: Fail to define %s(%d) which is already defined as `%s'",
error_name, error_code, desc);
}
}
errno_desc[error_code - ERRNO_BEGIN] = description;
return 0; // must
}
获取错误信息时首先查我们自己定义的数组然后再查是否时系统错误码。
const char* berror(int error_code) {
if (error_code == -1) {
return "General error -1";
}
if (error_code >= butil::ERRNO_BEGIN && error_code < butil::ERRNO_END) {
const char* s = butil::errno_desc[error_code - butil::ERRNO_BEGIN];
if (s) {
return s;
}
#if defined(OS_MACOSX)
const int rc = strerror_r(error_code, butil::tls_error_buf, butil::ERROR_BUFSIZE);
if (rc == 0 || rc == ERANGE/*bufsize is not long enough*/) {
return butil::tls_error_buf;
}
#else
s = strerror_r(error_code, butil::tls_error_buf, butil::ERROR_BUFSIZE);
if (s) {
return s;
}
#endif
}
snprintf(butil::tls_error_buf, butil::ERROR_BUFSIZE,
"Unknown error %d", error_code);
return butil::tls_error_buf;
}
错误码注册
#define BAIDU_CONCAT(a, b) BAIDU_CONCAT_HELPER(a, b)
#define BAIDU_CONCAT_HELPER(a, b) a##b
#define REGISTER_CODE(code, desp) \
\
const int BAIDU_CONCAT(error_code_flag, __LINE__) = \
DescribeCustomizedErrno((code), (desp)); \
template <> class ServiceCodeHelper<int(code)> {};
REGISTER_CODE(ServiceCode::kBadParams, "bad uri")
REGISTER_CODE(ServiceCode::kBadParams, "bad uri desp") // 重复定义