前言
Tiny-JSON 项目过程中所遇到的问题,做一个简单的整理。
反思
在项目开发过程中,主要采取的方法是自底向上的,忽略了顶层设计,而直接着手于具体的细节。这在项目开发过程中带来了一定的麻烦,导致时常需要重新设计或调整系统结构。
一方面来说,由于顶层设计依赖于底层的实现细节,采取自顶向下会有种无从下手的感觉,而对于详细的细节,不管是基础类的实现还是测试,都可以很有头绪的完成。但弊端就是基础类的设计对于整体系统来说,并不一定是最合适的,在后续的整合中,时常需要调整基础类的设计和实现。
另一方面,对这个系统的功能需求还不明确,许多功能在开发过程中才明确需要,这也就导致边开发边调整,需要来来回回修改甚至重构。由于本项目规模较小,修改和重构的代价是可以接受的,但如果项目规模较大,时间成本可能是无法接受的。
所以,在项目开始阶段,应该首先明确顶层设计和整体架构,这些类的具体功能并不需要马上实现,只需要有个简单的测试输出即可。然后在后续的开发阶段,补全这些具体功能,完成单元测试,最后在整合过程中进行集成测试。
switch 中的变量
在 switch
的 case
中定义变量,报 crosses initialization of 'xxx'
原因:在 case
中定义变量,如果没有大括号,则所有 case
都能访问得到,但其只在该 case
中进行了初始化,在其他 case
中访问的值是未定义的,所以报错。
解决方法:用 { }
包裹 case
,限定其变量的作用域。
类中成员的默认参数
在类中,默认参数要在定义函数时就写全,在实现中无需写出,否则报错,即使值一样
例:
class Test{
public:
void fcn(int, int = 2);
}
// 报错 default argument given for parameter 2 of 'void Test::fcn(int, int)'
void Test::fcn(int a, int b = 2){
...
}
// 正确,b的缺省默认值为 2
void Test::fcn(int a, int b){
...
}
命名空间中的变量
在命名空间中,直接定义变量会导致重复定义,报错 redefinition of 'xxx'
解决方式:在头文件只声明,在源文件定义
// .h
namespace name {
extern bool DEBUG;
}
// .cpp
bool name::DEBUG = false;
内部函数与外部函数
内部函数:只有该文件内能够调用(默认)
外部函数:该文件和其他文件都能调用
如果没有任何修饰符,源文件中的函数默认是内部函数,外部无法调用
声明外部函数:
// .h
extern void fcn();
// .cpp
void fcn(){
...
}
显式声明内部函数:
// .cpp
static void fcn(){
...
}
定义模板的别名
使用 using
关键字:
template <typename T> using ptr = std::unique_ptr<T>;
ptr<SomeClass> ptr1;
JSON 中的数据格式
字符串中的 \
需要再添加一个 \
进行转义,例如 \ -> \\
数字支持 1.23e+12
的科学计数法格式
委托构造函数
构造函数要进行委托时,放在参数列表后,不要放在函数体内,否则会产生错误
在构造函数内的 Test(...)
并不是期望中的委托构造函数,而是生成一个 Test
的临时对象
例如:
class Test{
public:
Test(int);
Test(double);
int i = 0;
};
Test::Test(int i): i(i){}
// 错误写法
Test::Test(double d){
Test(static_cast<int>(d));
}
// 正确写法
Test::Test(double d):Test(static_cast<int>(d)){}
int main(){
double d = 1.1;
int i = 2;
Test a(d); // 如果是错误写法,i 的实际值为 0
Test b(i);
return 0;
}
智能指针
- 对于一个对象,不要同时使用
shared_ptr
和unique_ptr
,智能指针销毁析构,会对原对象也进行析构,产生悬空指针。要一起使用shared_ptr
和unique_ptr
只能是unique_ptr
放弃所有权,交由shared_ptr
管理。 - 在类中不要使用
this
初始化一个智能指针,智能指针离开作用域就会析构this
,导致悬空指针。只有类继承enable_shared_from_this
再使用shared_from_this()
才能用this
初始化一个智能指针
在对象或方法内部使用时优先使用 unique_ptr
一个对象需要被多个 class
同时使用的时候用 shared_ptr
当出现循环引用时,一方使用 weak_ptr
make_shared
或 make_unique
方法,都会重新构造一个对象,原对象析构不影响。
智能指针的转化 dynamic_pointer_cast
C++ 字符串的坑
在参数类型中,如果重载函数参数列表是:
void fcn(bool);
void fcn(string&);
此时执行 fcn("abc")
会调用 fcn(bool)
必须要再声明一个重载函数 fcn(char[])
才能按预期调用,因为 "abc"
实际上是 char[]
而不是字符串,需要一步隐式转化,而匹配 bool
并不需要隐式转化。
union 的坑(Segmentation Fault)
union{
shared_ptr<xx> mem1;
shared_ptr<xx> mem2;
}
mem2 = make_shared<xx>(); // occurs Segmentation Fault
stackoverflow 上有一个相关的帖子,说的是当 =
不是 trivial 时,就会错误,而类型的赋值都不是 trivial 的,所以 union 不能放类,只能放指针或基础类型。
typename 和 class
在模板声明中,一般情况下,typename
和 class
没有区别,但涉及到模板推导时,需要使用 typename
显式告诉编译器这是个类型,而 class
无法做到这一点。