strcpy的几个焦点集中在一下几点:
是否效率优先
NULL指针处理
重叠(源和目的相同是其一种特殊情况)处理
字符串结束字符
字符类型
效率优先版本
extern "C" char * unchecked_strcpy(char *d,const char *s )
{
//the simplest and most efficient way
assert(d && s);
char* tmp = d;
while( *s)
*tmp++ = *s++;
*tmp = '/0';
return d;
}
这个版本已经没有什么好改动的地方了,在效率方面它可以得满分。注意extern “C”,不是随便写上去的,没错,它可以是一个C函数。作为一种低层程序库,完全有理由要求程序员自己控制好数组越界,空指针,野指针的情况,并将这些规格明确写于说明书上。
安全优先版本
extern "C" char * unchecked_strcpy_backward(char *d, const char *s )
{
//the simplest and most efficient way
assert(d && s);
size_t size = strlen(s);
char* tmp = d+size;
s+=size;
size++;
while( size )
{
*tmp-- = *s--;
--size;
}
return d;
}
extern "C" int strcpy_safe(char *d,
const char *s )
{
//return a exit code
//handle the overlapping situation
if(d == s)
return 1;//source and destination are the same
if(!d || !s)
return 2;//null string
size_t size = strlen(s);
//non-overlapped
if(d < s || d>s+size)
{
unchecked_strcpy(d,s);
return 0;//no error
}
//overlapped
unchecked_strcpy_backward(d,s);
return 0;//no error
}
unchecked_strcpy_backward实现逆向字符串拷贝。
strcpy_safe,处理了几种常见的情况,所以这样的效率是不高的。当源和目的相同时直接返回错误码1,但是我们想想在实际情况中,这种概率又能有多高?串指向空时,直接返回错误码2。重叠时,逆向拷贝,然而逆向拷贝一定就是正确的结果吗?不见得。只是我们按照普通思维认为这是正确的。
即使这样,还有两个安全问题无法解决,源或目的字符串是野指针,目的字符串的缓冲区溢出。从理论上说,这两种情况超越了C++的控制。
所以,从安全意义讲,永远没有一个安全的strcpy实现。我们不本着信任程序员的态度开发程序,永远也没有安全可言,这样只会给自己带来无尽烦恼。
完全定制版本
template<typename char_type>
struct NonNullStringChecker
{
static bool check(char_type* p1 , char_type* p2)
{
return true;
}
static void handle(char_type* p1 , char_type* p2)
{
}
};
template<typename char_type>
struct NonOverlappingChecker
{
static bool check(char_type* d,char_type* s)
{
return false;
}
static void handle(char_type* d,char_type* s)
{
}
};
template<
typename char_type = char,
char_type end_char = '/0',
class NullStringChecker = NonNullStringChecker<char_type>,
class OverlappingChecker = NonOverlappingChecker<char_type>
>
class strcpy_template
{
public:
char_type* operator()(char_type* d, char_type* s)
{
if(!NullStringChecker::check(d,s))
NullStringChecker::handle(d,s);
if(OverlappingChecker::check(d,s))
{
OverlappingChecker::handle(d,s);
}
else
{
char_type* tmp = d;
while( *s != end_char)
*tmp++ = *s++;
*tmp=end_char;
return d;
}
return 0;
}
};
是的,这是一个functor template,不是一个function template,因为function template不支持缺省参数。
从字符类型,字符串结束符号,空指针处理,重叠处理4个方面参数化这个类模版。当然,不能强迫所有人的模版技术都能达到Alexandrescu的水平(它把policy class和meta template programming在loki中运用的出神入化),所以必须提供一组缺省的参数给普通用户使用。
从程序看出NullStringChecker和OverlappingChecker都要提供check和handle接口。为了更安全的目的,你可以在check中做出各种条件检查,并在handle中做处理或者抛出异常。
这个应该是最具有弹性的版本,如果面试者看重template技术,那么这个版本一定能得一个高分。
总结
没有完美的解决方案。
公司的考察编程风格,思维严密程度,语言细节等等都太片面。编程风格是个人问题,强迫别人改变是一种不礼貌和粗鲁的行为,从深层次看,是一种不尊重人权的做法。而思维严密无非就是做错误检查,难道效率和安全是矛盾的,这个道理没人明白?在没有要求效率或者安全优先的情况下,任何一种实现方法都是正确的。考察思维的严密程度使用这种方法实在是愚蠢至极。如果要考细节,我倒想问问他们一些问题:一个编译单元的定义是什么?成员函数模版全特化版本在没有被调用时,编译器会为其生成代码吗?export关键字的用途?
我同意侯捷的“天下之事,必做于细”。但是,在编程方面未必可以以小见大。Lippman的《Inside C++ Object》和Alexandrescu的《Modern C++ Design》错误频繁,仍然不影响是经典。Scott Meyers到现在还没有准备写《Effective Template》,仍然不影响他是一个大师。无聊的人才以细节定成败。