禁止在构造函数里调用虚函数

简介: 禁止在构造函数里调用虚函数

在构造函数中调用虚函数会导致程序出现莫名其妙的行为,这主要是对象还没有完全构造完成。下面我们先来看一段代码:

class B
{
    protected B()
    {
        Method();
    }
    protected virtual void Method()
    {
        Console.WriteLine("B Method");
    }
}
class A:B
{
    private readonly string str = "你好";
    public A(string str)
    {
        this.str=str;
    }
    protected override void Method()
    {
        Console.WriteLine(str);
    }
    public static void Main()
    {
        var d = new A("A Method");
    }
}

在这里我要问一下各位读者,在上述代码中打印出的内容是什么?大部分读者会回答 “A Method” ,实际上的答案是 “你好” 。这是为什么呢?这是因为基类的构造函数调用一个定义在本类中的但是为派生类所重写的虚函数,程序运行的时候会调用派生类的版本,程序在运行期的类型是 A 而不是 B。在 C# 中系统会认为这个对象是一个可以正常使用的对象,这是因为程序在进入构造函数的函数体之前已经把该对象的所有成员变量都进行了初始化。但是者并不意味着这些成员变量的值和开发人员最终想要的值相符,因为程序仅仅执行了成员变量的初始化语句,而没有执行构造函数中的逻辑。

我们将前面的代码稍加修改看一下:

abstract class B
{
    protected B()
    {
        Method();
    }
    protected abstract void Method();
}
class A:B
{
    private readonly string str = "你好";
    public A(string str)
    {
        this.str=str;
    }
    protected override void Method()
    {
        Console.WriteLine(str);
    }
    public static void Main()
    {
        var d = new A("A Method");
    }
}

针对上述代码,我想问问各位读者,这段代码可以通过编译码?答案是可以通过编译,这是因为程序就不会创建一个类型为 B 的对象,他创建的对象只是 B 的实现了 Method 方法的子类,程序代码所运行的也是那个子类的 Method 方法。这么做主要是为了避免在构造函数中调用抽象类中的方法,防止抛出异常。虽然这么写可以避免这个问题但是还存在一个很大的缺陷,它会造成 str 这个对象在整个生命周期中无法保持恒定的值。在构造函数还没有把该对象初始化完成之前,它的取值是由初始化语句决定的,但是执行完构造函数之后它的值却变成了构造函数中所设定的那个值。派生类对象所具备的成员变量的默认值是由初始化语句或者系统来确定的,因此开发人员如果想要在构造函数中给这些变量赋值那么就必须等到程序运行到构造函数时才可以。


Tip:C# 对象的运行期类型是一开始就定好的,即便基类是抽象类也依然可以调用其中的虚方法。

小结

在基类构造函数中调用虚函数会导致代码严重依赖于派生类的实现,然后这些实现是无法控制且容易出错的。如果要避免错误,派生类就必须通过初始化语句把所有的实例变量设置好,但是这又会使得开发人员无法运用更多的编程技巧。也就是说在这种情况下派生类必须定义默认构造函数,并且不能定义别的构造函数,这将会给开发人员带来很大的负担。


目录
相关文章
|
4月前
|
C++
C++析构函数定义为virtual虚函数,有什么作用?
C++析构函数定义为virtual虚函数,有什么作用?
57 0
|
4月前
|
C++
49派生类的声明方式和构成
49派生类的声明方式和构成
39 0
|
编译器
C++11之继承构造函数(using 声明)
C++11之继承构造函数(using 声明)
207 0
【为什么】构造函数中可以调用虚函数吗?
【为什么】构造函数中可以调用虚函数吗?
|
安全 编译器 C++
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(二)
朋友们好啊,今天终于更新了。我是柠檬叶子C,本章将继续讲解C++中的面向对象的知识点,本篇主要讲解默认成员函数中的构造函数、析构函数和拷贝构造函数。还是和以前一样,我们将由浅入深地去讲解,以 "初学者" 的角度去探索式地学习。会一步步地推进讲解,而不是直接把枯燥的知识点倒出来,应该会有不错的阅读体验。如果觉得不错,可以 "一键三连" 支持一下博主!你们的关注就是我更新的最大动力!Thanks ♪ (・ω・)ノ
89 0
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(二)
|
编译器 C++
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(一)
朋友们好啊,今天终于更新了。我是柠檬叶子C,本章将继续讲解C++中的面向对象的知识点,本篇主要讲解默认成员函数中的构造函数、析构函数和拷贝构造函数。还是和以前一样,我们将由浅入深地去讲解,以 "初学者" 的角度去探索式地学习。会一步步地推进讲解,而不是直接把枯燥的知识点倒出来,应该会有不错的阅读体验。如果觉得不错,可以 "一键三连" 支持一下博主!你们的关注就是我更新的最大动力!Thanks ♪ (・ω・)ノ
118 0
【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数(一)
|
编译器 C++
【一、构造函数与析构函数】深度解析C++类的构造函数与析构函数调用机制
【一、构造函数与析构函数】深度解析C++类的构造函数与析构函数调用机制
445 0
|
C++
同样一句代码,在类内调用,跟类外调用结果不同?
同样一句代码,在类内调用,跟类外调用结果不同?
72 0
派生类和析构函数的构造规则
派生类和析构函数的构造规则
141 0
|
编译器 C++
C++不要在构造或析构函数中调用虚函数
C++不要在构造或析构函数中调用虚函数
279 0