64,651
社区成员
发帖
与我相关
我的任务
分享
int ExampleCallback(int error,const char* data, int size,void* usr)
{
if(error){
return error;
}
//调用方直接在回调中使用data处理自己的业务。
return 0;
}
//使用回调接收数据,直接在上面回调中处理结果
ExampleFunc(params, size_in, &ExampleCallback, NULL);
请理解我所说的方案3的 接口设计理念。 它做到了:在接口设计时,完全不用关心调用方如何如何,不用担心调用方知不知道该申请多少内存给我(方案1),也不用担心调用方会不会忘记调用我提供的另一个释放接口(方案2)。一旦担心的事情发生了,那我认为,被调方是需要承担责任的(接口设计不合理)。而方案3就不一样了,的确,在我之前说的例子中,调用方可能因业务需要,还是要在回调中申请内存保存结果,在回调外处理,那调用方还是可能忘记释放。但那又怎么样呢?关我什么事?那是你调用方自己的问题,我认为怪不到被调方的头上。
看下面这个例子:
int ExampleCallback(int error,const char* data, int size,void* usr)
{
if(error){
return error;
}
//接收data,无需关心data的释放。
char** p_data_out = (char**)usr;
*p_data_out = new char[size];
memcpy(*p_data_out, data, size);
return 0;
}
//回调外部,调用方代码:
char* data_out = NULL;
//使用回调接收数据
ExampleFunc(params, size_in, &ExampleCallback, &data_out);
//使用数据data_out
//...
//如果调用方忘记释放data_out,那就是调用方自己的责任。因为是调用方自己new的,new和delete都明确的在调用方模块内。
//delete data_out;
不过按上述例子写代码,是容易忘记delete的,因为new的代码和delete的代码虽然在同一模块内,但仍分割在了两个地方。所以我之前的例子是使用了string或者其他自己的数据结构,但即使不用,一旦出了问题,就不能怪罪到接口设计问题上了吧?接口设计的已经能够保证接口自己的内存一定被释放了,它的任务就完美完成了。剩下的是调用方自己代码问题了。
我们再来看回方案1和2:
方案1,接口直接不考虑申请内存,这个任务直接甩给调用方,当然不用担内存忘记释放的责任了,但是带来了另一个问题:调用方不知道申请多少内存。这一点是不合理的地方。
方案2,提供额外接口用于释放内存。这要求调用方明确知道这一规则,一个功能健壮的完成需要调用两个接口,这本身不太符合开发者习惯的。这一点是不合理的地方。开发者如果忘记调用释放接口,那么我认为,采用这种方案的接口设计者还是要承担一点责任的。
当然,方案3的设计就一定完美了吗?也不是。回调这种东西,多了之后,代码分割严重,会使得难以阅读。但我觉得相比较而言,我更喜欢方案3。
/**
* @param const char* [in] 输入参数;
* @param int [in] 输入参数长度;
* @param char* [in] 输出参数,返回内容;
* @param int* [in,out] 输入输出参数,用户输入时指定data_out内存空间的长度,函数返回输出实际内容长度;
* @return 成功返回0,否则返回其他值;
*/
int ExampleFunc(const char* params,int size_in, char* data_out, int* out_size);
这种方案,需要调用方事先申请足够大的内存,所以接口一般还要设计成:接口因为调用方输入内存空间不够用时,告知调用方所需的内存大小。
所以,调用方可能需要调用两遍接口。例:
//第一遍确定所需内存空间
int out_size = 0;
ExampleFunc(params,size_in,NULL,&out_size);
//调用方申请内存
char* out_buf = new char[out_size];
//再次调用
ExampleFunc(params,size_in,out_buf,&out_size);
//...
//调用方释放
delete out_buf;
方案2:接口内部申请空间,并提供额外的接口用于释放这片内存。
例子:
/**
* @param const char* [in] 输入参数;
* @param int [in] 输入参数长度;
* @return 返回内容,使用完毕后需要调用FreeExample接口释放。
*/
char* ExampleFunc(const char* params, int size_in);
/**
* @param const char* [in] 输入参数;ExampleFunc的返回值
* @return 成功返回0,否则返回其他值;
*/
int FreeExample(const char* params);
该方案的缺点是,需要额外再设计一个接口供调用方使用,增加了调用方开发成本,且调用方很容易忘记这茬,甚至不知道还有个释放接口。
调用例子:
//调用功能,被调用方申请了内存并返回了结果。
char* out_buf = ExampleFunc(params,size_in);
//调用方使用
//...
//使用完毕后,调用专门的接口,由被调方释放
FreeExample(out_buf);
方案3:使用回调返回内容
例子:
/** @brief 功能接口调用完成通知;
* @param error [in] 错误码;
* @param data [in] 数据结果;
* @param size [in] 数据长度;
* @param usr [in] 用户数据;
* @return 成功返回0,失败返回其他值;
*/
typedef int (*COMPLETIONPROC)(int error,const char* data, int size,void* usr);
/**
* @param const char* [in] 输入参数;
* @param int [in] 输入参数长度;
* @param COMPLETIONPROC [in] 用于接收结果的回调函数地址;
* @param void* [in] 用户数据,会在在回调中原样返回;
* @return 成功返回0,否则返回其他值;
*/
int ExampleFunc( const char* params, int size_in, COMPLETIONPROC proc,void* usr);
个人比较推荐这种方案,该方案用户无需事先申请内存,就不需要事先知道需要多少内存大小。也不需要关心是否还有其他接口专门用来释放。并且该方案还能支持异步调用。
调用方调用例子:
//借用std::string接收内容,也可以是自己的数据类
std::string data_out;
//使用回调接收数据
ExampleFunc(params, size_in, &ExampleCallback, &data_out);
//使用数据data_out
//...
调用方实现的回调:
int ExampleCallback(int error,const char* data, int size,void* usr)
{
if(error){
return error;
}
//使用自己的数据结构接收data,无需关心data的释放。
std::string* p_data_out = (std::string*)usr;
p_data_out->append(data, size);
return 0;
}
该方案下,被调方内部实现的一个简单例子:
int ExampleFunc( const char* params, int size_in, COMPLETIONPROC proc,void* usr)
{
//解析params
//...
//size_out = xxx;
//...
//根据params,生成结果data_out
char* data_out = new char[size_out];
//...
//memcpy(data_out,xxx,size_out);
//...
//返回结果
if(proc)
{
ret = proc(0, data_out, size_out, usr);
}
//释放内存
delete data_out;
return ret;
}