导读
最近做项目,深感自己C++功底不足。故开始看Effective C++。
该【Effective C++】系列为笔记。
少用define
现在我们用#define
,主要是两种用途:
- 定义常量
- 定义函数
对于第一种,书中推荐使用const
,如果是类常量,那么使用static const
。这是因为#define其实是一个预处理,在编译期间就会被替换掉,不会进入符号表。
这里需要注意:
- 常量字符串需要「两个const」,比如
const char* const MESSAGE = "HelloWorld";
,才能同时保证指针和对象本身都是常量 - 类常量为了保证一个类只有一个,所以需要是static的。类常量除了在类内声明外,还需要在实现文件中定义。.
而对于想用define来实现功能函数的,同样不建议。原因有是这样的假函数没有自己的定义域,从而:
可以改变参数本身的值
1
2
3
4
5
6
7
8
9
return ++Value;
void Bar()
{
int a = 1;
Foo(a);
Foo(a); // a = 3
}如果定义了新的变量,无法在同一个定义域里调用多次该宏,比如:
1
2
3
4
5
6
7
8
int a = 1
void Bar()
{
Foo();
Foo(); // compile error 定义了两个同名变量a
}
如果我们是因为难以确定参数的类型而苟且使用泛型,完全可以用 Inline Template Function 来实现。更高更快更强,更优雅。
1 | // 这么写的原因是Callback可能是各种各样的类型,无法确定 |
换成Inline Template Function的形式:
1 | template<class T> |
update on 20200914
更新一个新鲜的使用宏的bug
先说结论: 宏的定义是全局的,即使不被include,也会生效。所以不要乱用宏,如果要写宏,那么也要写的容易辨认一些,比如全大写命名。
之前在写一个辅助添加监听的函数,那时还没有了解template编程,纠结于监听回调的类型问题,于是用了宏来实现,大概是这样的:
1 |
|
这一切都没啥问题。
直到我后来接触了template,知道这种不明参数类型的函数应该用template function来实现更为恰当,于是设计了一个EventHelper
类:
1 | class EventHelper |
用起来感觉良好,编译成功。
但是,我没有把原来的宏删除掉。
commit代码之后伙伴更新到了我的代码,然后在EventHelper::AddListener
那里报了许许多多奇怪的语法错误。
问题是光看EventHelper::AddListener
是完全没看出问题的。
后来一步步排查,只要把我原来定义的AddListener
宏删掉,就能编译通过了。
得到结论是在编译的时候,编译器把EventHelper::AddListener
中的AddListener
字样替换成宏的内容了,所以会直接报错。
所以我们后面用宏的时候要注意:
- 尽量不要用宏
- 用了宏,名字要有辨识性,比如
MACRO_ADD_LISTENER()
- 注意宏和define都是全局的,不要乱写
template
说到模板方法,这里提一点新手容易犯的错。
我们平时都是将函数的声明放在.h文件,将实现放在.cpp文件,用的时候include.h文件。
对于template函数来说,不能在.cpp文件中实现,否则链接的时候会出现unresolved external symbol
。找问题的时候简直都怀疑自己的眼睛了,明明有定义,但是确说找不到符号。
Google之,关键字「c++ template function unresolved external symbal」。查到StackOverflow的帖子。
这里面说:模板类和函数在被用到之前是不会被实例化的。当模板被用到的时候,编译器需要模板的完整定义(而非只是声明)来检查正确的类型。
所以, 模板方法的定义需要写在头文件中,并且使用的时候需要include这个头文件 。
多用const
指向常量的指针 vs 常指针
指向常量的指针:指向的对象是常量,不可改变。const在星号的前面,如const AActor* MyActor;
。
常指针:指针本身是常量,不可以指向别的对象。const在星号的后面,如AActor* const MyActor;
。举例:
- 用于STL容器返回的迭代器,返回一个
T* const
指针,不可以指向别的对象;
常指针常量:指针和对象都是常量,需要两个const,如const char* const MESSAGE = "HELLO WORLD";
。
用于修饰函数返回值,避免函数返回值被修改
例子:
1 | class Rational {...} |
设想如果返回的不是一个常量,那么我们就可以对其作出修改
1 | Rational a, b, c; |
而如果返回的是一个const值,就可以避免这种情况。