模板实参推断
将实参传递给带模板类型的函数形参时,能自动类型转换的只有const转换和数组或函数到指针的转换,(顶层const在形参、实参中都被忽略),算数转换、派生类向基类的转换、用户定义的转换都不行。
尾置返回类型:
template <typename It>
auto func(It beg, It end) -> decltype(*beg){
return *beg;
}
右值引用形参可以接受右值或者左值,导致推断不同:
template <typename T>
void f(T &&){
}
int i = 0;
f(i); //如果实参是左值,则T被推导为左值引用(int&), 形参(T& &&)也折叠为左值引用(int&)
f(0); //如果实参是右值,则T被推导为(int),形参是右值引用(int &&)
move的实现:其实就是将左值转换成右值引用,其他什么都没做。
template <typename T>
typename remove_reference<T>::type&& move(T&& t){
return static_cast<typename remove_reference<T>::type&&>(t);
}
完美转发:std::forward<T>()
返回T的右值引用(可能发生折叠,返回左值引用)
template<typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2){
f(std::forward<T2>(t2), std::forward<T1>(t1)); //完美保持t1和t2的属性
}
如果实参是右值,则T是普通类型,forward将返回右值引用(T&&)
若果实参是左值,则T是左值引用类型,forward返回左值引用的右值引用(折叠成左值引用)
折叠规则:
X& &
,X& &&
,X&& &
都折叠成左值引用X&& &&
折叠成右值引用
关于右值引用和move:https://www.zhihu.com/question/50652989
string s1 = "move";
string p = std::move(s1); //触发string的移动构造函数,s1置为空
string s2 = "not";
string&& p2 = std::move(s2); //p2只是s2的右值引用,没有除法移动构造函数,s2非空
重载、覆盖、隐藏
C++中函数调用的解析过程:p.func();
- 首先确定p的静态类型
- 在p的静态类型对应的类中查找
func
,如果找不到,沿着继承链不断查找至顶端,如果没有就报错 - 找到
func
后,进行参数类型检查 - 如果调用合法
- 如果
func
是虚函数且p是引用或者指针,则将在运行时决定运行该虚函数的哪个版本 - 否则,编译器将产生一个函数调用(编译器就决定调用哪个)
- 如果
名字查找优先于类型检查,如果在派生类中找到了与基类同名但是参数不同的函数,仍会隐藏基类函数(而不会发生重载),不会继续从基类中查找类型匹配的函数调用。
参考:https://www.cnblogs.com/txwsh1/archive/2008/06/28/1231751.html
成员函数被重载的特征
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
class Base{
public:
virtual int fcn();
};
class D1: public Base{
int fcn(int); //隐藏基类的fcn,不是虚函数;同时继承了Base::fcn();
virtual void f2(); //新的虚函数
};
class D2: public D1{
int fcn(int); //非虚函数,隐藏了D1::fcn(int);
int fcn(); //覆盖了Base::fcn();
void f2(); //覆盖了D1::f2();
}
类型转换
dynamic_cast
: 如果运行时信息(RTTI)转换前后一致就可以转换, 否则置nullptr(比如子类转父类)
static_cast
:和c语言一样,没有运行时检查,不能去掉底层const(报错)
reinterpret_cast
: 为运算对象的为模式提供较低层次上的重新解释
const_cast
: 可以用来去掉const、volatile
拷贝构造函数调用时机:
- 等号时
- 非引用传参
- 函数非引用返回对象
- 列表初始化结构体
如果构造函数是explict的,那么必须显式调用构造函数(不能依靠拷贝构造函数)
编译器可能会将拷贝初始化改为直接初始化。
拷贝赋值运算符在赋值时被调用
阻止拷贝
NoCopy() == default; //合成构造函数
Nocopy(const NoCopy&) = delete; //阻止拷贝
NoCopy &Nocopy(const NoCopy&) = delete; //阻止赋值
~NoCopy() = default; //合成析构函数
不要返回栈上的引用或指针
不要返回堆上的引用(很容易造成内存泄漏),比如下面这个例子
derived& func(){
derived *d = new derived;
cout<<"func_d:"<<d<<endl;
return *d;
}
int main(){
//正确的释放内存
auto todo = &func(); //
cout<< todo <<endl;
delete todo;
//错误的
auto d = func(); //此时调用了拷贝构造函数,这时已经无法(合理的)得到func()函数中分配内存的指针了,无法释放内存
}
可以使用valgrind检测内存泄漏情况:
valgrind --tool=memcheck --leak-check=full ./main_c
c++ 返回局部变量:http://www.samirchen.com/function-returns/
右值引用:https://www.cnblogs.com/tingshuo/archive/2013/01/22/2871328.html
将RVO优化关闭,可以对g++增加选项-fno-elide-constructors
,否则编译器会对函数返回值进行优化不再创建临时对象(不再调用复制构造函数)
可以使用final和override限制或者强制覆盖,以免发生错误。
派生类的虚函数将使用基类虚函数的默认实参。因此写程序时最好保持默认实参一致。
纯虚函数的定义必须在类体的外部。
关于extern
和static
:
- 将extern声明在头文件中,在某个源文件中定义一次。
- 将static定义在源文件中。如果定义在头文件中,每次引入头文件都会产生新的的定义(不是同一个对象)。
关于指针和引用:
编译器会给引用分配地址,不过对用户隐藏了。
https://stackoverflow.com/questions/57483/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable-in
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
ssert(p == p2);