最近遇到了几个C++问题,在这里总结一下,希望可以避免其他朋友犯同样的错误。
一.隐式转换引发的血案
我们直接来看一段代码:
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
using namespace std;
void a(bool input)
{
cout<<"I amd first"<<endl;
cout<<input<<endl;
}
void a(const string &input)
{
cout<<"I amd second"<<endl;
cout<<input<<endl;
}
int main(int argc,char **argv)
{
a("str"); // 是调用第二个a函数吗?
a(string("str"));
return 0;
}
运行结果会是啥呢?好吧,可能让你失望了,结果如下:
I amd first
1
I amd second
str
char*类型的"str"居然被隐式转化成了bool类型,我简单做了一下测试:
#include <string>
#include <vector>
#include <set>
#include <map>
using namespace std;
int main(int argc, const char *argv[])
{
int a = 1;
char * b = "wo";
float c = 1.1;
bool x;
x = a;
x = b;
x = c;
return 0;
}
int,char*,float确实都可以隐式转化成bool而不会有任何警告,导致出现问题很不容易发现,这里在重载函数的时候确实需要万分注意。
二.字节对齐引发的惨案
先来介绍一下背景,我这里有个server,会在运行时调用一个so,正常情况下都一切正常,但是在引用了一个第三方给的头文件之后,在调用so的一个函数的时候就会core掉。 百思不得其解之下,去看了一下那个第三方头文件的定义:
#ifndef HEADER_OPENAPILOG_PROTOCOL
#define HEADER_OPENAPILOG_PROTOCOL
#pragma pack(1)
//一些结构体定义
#endif
只调用了#pragma pack(1)却没有在文件结束时调用#pragma pack()!而我的主调server虽然重新编译了,但是so却没有重新编译,导致其共用的结构体一个做了字节对齐,一个没有做。 修改成如下即正常:
#ifndef HEADER_OPENAPILOG_PROTOCOL
#define HEADER_OPENAPILOG_PROTOCOL
#pragma pack(1)
//一些结构体定义
#pragma pack()
#endif
每个复杂的问题背后都有一个简单的原因,OK,就这样。
congyang on #
第一个确实很容易出错。隐式转换还是很容易出错的。。。
看来重载时还是要慎用bool
Reply
Dante on #
确实如此。。。
Reply
terrence on #
第一个问题可能是这样的,"string"是一个const char *类型的,转成bool是没办法的办法。如果同时定义了a(bool b)和a(const char* str)这两个,那么调用的是后者。所以是"string"默认转const char *,后转bool,最后才是string。这个顺序我猜可能和具体的编译器有关,可能C++标准中没有定义隐式类型转换的优先级。
Reply
Dante on #
嗯,我的理解也可能是编译器内置的隐式转化优先级会高于类构造函数的入参的优先级。。。
Reply
terrence on #
隐式转换很危险,像刚才的例子,找不到直接和"string"相匹配的a函数,就要先隐式转换再重新查找函数的签名,如果存在多个可行的候选就很麻烦。写出高质量的代码不容易~
Reply
baby stroller on #
不是计算机专业的,所以对c语言没有什么了解过。上面的内容看了一头雾水。
Reply
megax on #
你这么写应该就是错的吧!string绝对不可和const char*化等号,operator cosnt char*()也只是string的一个函数而已。而"str"其实和int没啥区别,隐式转化为true
Reply
Dante on #
string是有一个构造函数,参数是const char *的。。
Reply
megax on #
和构造函数么没有关系的,比如你传递point的时候,也是使用CPoint(10,10),而不是(10,10)。所以也应该使用string("str")传递。因string太通用,很多人忽视了其实它是一个类。所以在c#和java中,string都被设计成了一个特殊类。让你觉得它和Int,float没啥区别。
Reply
Dante on #
看了这段代码你应该就明白了
<pre lang="cpp" line="1">
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
using namespace std;
class CTest
{
public:
CTest (int a)
{
printf("a:%d\n",a);
}
private:
};
int test(CTest x)
{
printf("good\n");
}
int main(int argc, const char *argv[])
{
test(1);
return 0;
}
</pre>
输出是:
<pre lang="txt" line="0">
a:1
good
</pre>
函数的重载已经不止是类型的隐式转换,还要考虑到面向对象编程里的类的构造函数参数。
Reply
Saalihmao on #
C++里面单参数的构造函数一定要声明为explicit
隐式转换除了用来构造库的死后可以让应用代码看上去很科幻之外没有任何好处。
Reply
Dante on #
确实如此,隐式转化带来的问题非常多,稍不留神就会掉入陷阱。
Reply
freeeyes on #
你好,看了你的博客,觉得非常的好,很想和你一起切磋技术,文章原创很难,楼主写了这么多很不容易,我专门研究服务器开发的,希望能和你成为朋友,我的QQ是41198969。
Reply
Dante on #
已经添加,我QQ虽然在线不过一般不聊天哦。如果方便可以加一下我的gtalk,邮箱在联系的页面。
Reply
天下懒鬼 on #
我还以为第一个问题只会在讲编程规范的书中出现,原来还真有可能遇到的呀。好像是<>中说,为了提高效率,如果你有一个函数中如果你有一个参数是string const &,你就最好要写一个参数是char const*的重载函数。现在看来,就这条规则,还有助于避免这类问题。
Reply
天下懒鬼 on #
vimer老师,新年快乐,看问题都忘记问候了
Reply
Dante on #
哈哈,新年快乐!
说得有道理,如果同时重载const char*也就不会有问题了~
Reply
天下懒鬼 on #
拿问题到群上讨论了一下,有人给出了这样子的解释,我们可以写这样子的代码
char const * p = "hello";
if (p)
{
std::cout << p << std::endl;
}
这个就是字符指针可以隐式转换为bool类型的原因。而且不仅字符指针可以,其他任何类型的指针都可以这样子转换。所以重载的时候一定要小心。
Reply
Heartwork on #
还真没注意到,好像是表达式的结果直接就是布尔值,所以避免了类型转换。
所以还是不要以布尔值作为参数吧,或者尽量避免隐式类型转换。
Reply
Heartwork on #
这样使用pragma相当于没有字节对齐,虽然会节省一点内存,但是会增加memory/cache存取次数。
Reply
wangjieest on #
编译器查找函数候选集
先选同名函数
再查找直接参数匹配,
再查找隐式转换匹配,
而隐式转换匹配肯定是基础类型优先啊,
而基础类的排序我也没搞懂,当没有重载bool()而重载了void* ()时,系统如何帅选出void*()的...
例如 一些平台上的 if(cin)的实现
Reply
wangjieest on #
详见 C++ primer 参数类型转换 相关内容
Reply