父类构造函数可以调用虚函数么,RTTI,lambda底层实现

父类构造函数是否可以调用虚函数

在父类的构造函数中调用虚函数是可以的,但是不会实现多态。

这是因为在定义子类对象的时候,会先调用父类的构造函数,而此时虚函数表以及子类函数还没有被初始化,为了避免调用到未初始化的内存,c++标准规范中规定了在构造子类时调用父类的构造函数,而父类的构造函数中又调用了虚成员函数,即使这个虚成员函数被子类重写,也不允许发生多态的行为,所以使用的是静态绑定,调用了父类的函数。

class A {
public:
    A() { show(); }
    virtual void show() { cout << "A" << endl; }
};
class B :public A{
public:
    B() { show(); }
    virtual void show() { cout << "B" << endl; }
};
//输出 A B

C++的RTTI机制

RTTI概念

RTTI(run time type identification)即通过运行时类型识别,程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型

RTTI机制的产生

为什么会出现RTTI这一机制,这和C++语言本身有关系。C++是一种静态类型语言,其数据类型在编译期就确定了,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用本身的类型,可能与它实际代表(指针或引用)的类型并不一致。有时候我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就产生了运行时类型识别的要求

C++要获得运行时类型信息,只能通过RTTI机制,并且c++最终生成的代码是直接与机器相关的。

dynamic_cast和typeid

  • typeid运算符,用于返回表达式的类型;
  • dynamic_cast运算符,用于将基类的指针或引用安全地转换成派生类的指针或引用;

当我们将这两个运算符用于某种类型的指针或引用,并且该类型含有虚函数时,运算符将使用指针或引用所绑定对象的动态类型。

这两个运算符特别适用于以下情况:

  • 我们想使用基类对象的指针或引用执行某个派生类操作并且该操作不是虚函数。 一般来说,只要有可能我们应该尽量使用虚函数。当操作被定义成虚函数时,编译器将根据对象的动态类型自动地选择正确的函数版本。然而,并非任何时候都能定义一个虚函数。另一方面,与虚成员函数相比,使用RTTI运算符蕴含更多潜在的风险:程序员必须清楚地知道转换的目标类型并且必须检查类型转换是否被成功执行。
dynamic_cast运算符
//使用形式如下
dynamic_cast<type*>(e);		//e必须是一个有效指针
dynamic_cast<type&>(e);		//e必须是一个左值
dynamic_cast<type&&>(e);	//e不能是左值

type必须是一个类类型,并且通常情况下应该含有虚函数。

上面的所有形式中,e的类型必须符合以下三个条件中的任意一个:

  • e的类型是目标type的公有派生类
  • e的类型是目标type的公有基类
  • e的类型是目标type的类型

如果符合,则类型转换成功;否则,转换失败。如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0;如果转换目标是引用类型,并且失败了,则抛出一个bad_cast异常

指针类型的dynamic_cast

假定Base类至少含有一个虚函数,DerivedBase的公有派生类。如果有一个指向Base的指针bp,则我们可以在运行时将它转换成指向Derived的指针。

if(Derived* bp = dynamic_cast<Derived*>(bp))
{
    //使用bp指向的Derived
}
else{	//bp指向一个Base对象
    //使用bp指向的Base对象
}

可以对一个空指针执行dynamic_cast,结果是所需类型的空指针。

在条件部分执行dynamic_cast操作可以确保类型转换和结果检查在同一条表达式中完成。

引用类型的dynamic_cast

当对引用的类型转换失败时,程序抛出一个名为std::bad_cast的异常。

void f(const Base& b)
{
    try{
        const Derived& d = dynamic_cast<const Derived&>(b);
        //使用b引用的Dervied对象
    }catch(bad_cast){
        //处理类型转换失败的情况
    }
}

typeid运算符

typeid表达式的形式是typeid(e),其中e可以是任意表达式或类型的名字。typeid操作的结果是一个常量对象的引用。

typeid运算符可以作用于任意类型的表达式。顶层const被忽略,如果表达式是一个引用,则typeid返回该引用所引用对象的类型。不过当typeid作用于数组或函数时,并不会执行向指针的标准类型转换。也就是说,如果我们对数组a执行typeid(a),得到的结果是数组类型而非指针类型。

当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid运算符指示的是运算对象的静态类型。当运算对象是定义了至少一个虚函数的类的左值时,typeid的结果直到运行时才会求得。

通常情况下,使用typeid比较两个表达式的类型是否相同。

Derived* dp = new Derived;
Base* bp = dp;

if(typeid(*bp) == typeid(*dp)){
    //bp和dp指向同一类型的对象
}
//检查运行时类型是否时某种指定的类型
if(typeif(*bp) == typeid(Derived)){
    //bp实际指向Derived对象
}

注意,typeid应该作用于对象,因此我们使用*bp而非bp

//下面的检查永远是失败的,bp的类型是指向Base的指针
if(typeid(bp) == typeid(Derived))
{
    //永远不会执行
}

typeid作用于指针时(而非指针所指的对象),返回的结果是该指针的静态编译时类型。

typeid是否需要运行时检查决定了表达式是否会被求值。只有当类型含有虚函数时,编译器才会对表达式求值。反之,如果类型不含有虚函数,则typeid返回表达式的静态类型;编译器无须对表达式求值也能知道表达式的静态类型。

如果表达式的动态类型可能与静态类型不同,则必须在运行时对表达式求值以确定返回的类型。 如果指针p所指的类型不含有虚函数,则p不必非得是一个有效的指针。否则,*p将在运行时求值,此时p必须是一个有效的指针。如果p是一个空指针,则typeid(*p)将抛出一个名为bad_typeid的异常。

lambda底层实现原理

[捕获列表](形参列表) mutable 异常列表->返回类型
{
    //函数体
}

捕获列表:捕获外部变量,捕获的变量可以在函数体中使用,可以省略,既不捕获外部变量;

形参列表:和普通函数的形参列表一样

mutable:mutable关键字,如果有,则表示在函数体中可以修改捕获变量,根据具体需求决定是否需要省略

异常列表:noexcept/throw(…),和普通函数的异常列表一样,可省略,即代表可能抛出任何类型的异常

返回类型:和函数的返回类型一样,可省略,如省略,编译器将自动推导返回类型

使用实例

void LambdaDemo()
{
	int a = 1;
    int b = 2;
    auto lambda = [a, b](int x, int y) mutable throw()->bool
    {
        return a+b>x+y;
    };
    bool ret = lambda(3,4);
}

编译器实现原理

编译器实现lambda表达式大致分为以下几个步骤:

  • 创建lambda类,实现构造函数,使用lambda表达式的函数体重载operator()
  • 创建lambda对象
  • 通过对象调用operator()

编译器将lambda表达式翻译后的代码

class lambda_xxx
{
private:
    int a;
    int b;
public:
    lambda_xxx(int _a, int _b) : a(_a),b(_b){}
    bool operator()(int x, int y) const throw()
    {
        return a+b>x+y;
    }
};
void LambdaDemo()
{
	int a = 1;
    int b = 2;
    lambda_xxx lambda = lambda_xxx(a, b);
    bool ret = lambda.operator()(3,4);
}

lambda_xxx与lambda表达式的对应关系

  • lambda表达式中的捕获列表,对应lambda_xxx类的private成员
  • lambda表达式中的形参列表,对应lambda_xxx类成员函数operator()的形参列表
  • lambda表达式中的mutable,对应lambda_xxx类成员变量是否可以在operator()函数中修改
  • lambda表达式中的返回类型,对应lambda_xxx类成员函数operator()的返回类型
  • lambda表达式中的函数体,对应lambda_xxx类成员函数operator()的函数体

另外,lambda表达式捕获列表的捕获方式,也影响对应lambda_xxx类的private成员的类型

  • 值捕获:private成员的类型与捕获变量的类型一致
  • 引用捕获:private成员的类型是捕获变量的引用类型

无捕获列表和参数列表

auto print = []{};
//翻译
class printClass{
public:
	void operator()(void) const
    {
        
    }
};
//用构造的类创建对象,print此时就是一个函数对象
auto print = printClass();

无捕获列表但有参数列表

auto add = [](int a,int b){ return a+b; };
//翻译
class addClass{
public:
    auto operator(int a, int b) const
    {
        return a+b;
    }
};
auto add = addClass();

值捕获

int year = 2014;
int month = 12;
auto print = [=](){ cout<<year<<month<<endl; };
//翻译
class printClass{
public:
    printClass(int year, int month) : year(year), month(month){}
    void operator()(void) const
    {
        cout<<year<<month<<endl;
    }
private:
    int year;
    int month;
};
auto print = printClass(year, month);

引用捕获

int year = 2014;
int month = 7;
auto print = [&](){ year++; cout << year<< month<<endl; };
//翻译
class printClass
{
public:
    printClass(int& year, int& month) : year(year), month(month){}
    void operator()(void) const
    {
		year++;		//const对引用类型无效
        month++;
        cout<<year<<month<<endl;
    }
    
private:
    int& year;
    int& month;
};

混合捕获

int year = 2019;
int month = 11;
auto show = [&, month]() mutable { month++; year++; cout<<year<<month; }
//翻译
class showClass{
private:
    int& year;
    mutable int month;
public:
    showClass(int& year, int month) : year(year),month(month) {}
    void operator() const{
        month++;
        year++;
        cout<<year<<month;
    }
};
auto show = showClass(year,month);
show();

lambda和函数指针

一个没有指定任何捕获的lambda函数,可以显式转换成一个具有相同声明形式的函数指针。

auto lambda = [](int a){}
void(*funcPtr)(int) = lambda;
funcPtr(4);
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值