C++右值引用与move

模板实参推断

将实参传递给带模板类型的函数形参时,能自动类型转换的只有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

类型转换:https://blog.csdn.net/ydar95/article/details/69822540

将RVO优化关闭,可以对g++增加选项-fno-elide-constructors,否则编译器会对函数返回值进行优化不再创建临时对象(不再调用复制构造函数)

可以使用final和override限制或者强制覆盖,以免发生错误。

派生类的虚函数将使用基类虚函数的默认实参。因此写程序时最好保持默认实参一致。

纯虚函数的定义必须在类体的外部。

关于externstatic

  • 将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);
发布了74 篇原创文章 · 获赞 11 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yijiull/article/details/89395424
今日推荐