为啥 C++ 不能用“超类”类型的右值初始化“派生类”类型的变量?

Posted

技术标签:

【中文标题】为啥 C++ 不能用“超类”类型的右值初始化“派生类”类型的变量?【英文标题】:Why C++ cannot initialize a variable of type "derived class" with an rvalue of type "super class"?为什么 C++ 不能用“超类”类型的右值初始化“派生类”类型的变量? 【发布时间】:2011-07-15 14:28:19 【问题描述】:

请考虑以下代码:

class a

    int a1;

    public:
    a()
    
        printf("foo1\n");
    
;

class b : public a

    int a2;
    public:
    b()
    
        printf("foo2\n");
    
;
int main (int argc, const char * argv[])

    b *instance = new a();
    return 0;

它给出错误:无法使用“a*”类型的右值初始化“b*”类型的变量 我写的时候效果很好

a *instance = new b();

输出是:

foo1
foo2

谁能解释一下原因?我将非常感激:)

另一件事,如果我写了

instance->~a();

return 0; 之上没有任何额外的事情发生。这是因为构造函数只能调用一次吗?

【问题讨论】:

~a 是析构函数,而不是构造函数。直接调用其中任何一个都很奇怪。 语句instance->~a(); 调用析构函数,而不是构造函数。您通常不需要手动执行此操作。对于使用new 分配的对象,请使用delete 运算符,如delete instance; 我只是在学习。我想澄清一些疑问。对不起,如果我伤害了你。感谢您的回答。 我可以看到我的问题的 -1s。我认为我冒犯了社区。再次抱歉,感谢您的回答。 你不应该显式调用析构函数。唯一需要这样做的情况是对象是使用placement new 运算符构造的。 【参考方案1】:

让我们更具体一点:

class Animal

    int a1;

    public:
    Animal()
    
        printf("Animal\n");
    
;

class Bat : public Animal

    int a2;
    public:
    Bat()
    
        printf("bat\n");
    
;
int main (int argc, const char * argv[])

    Bat *instance = new Animal();
    return 0;

您现在明白为什么这可能无效了吗?您使用new Animal() 创建的内容可以是任何类型的动物。将其分配给Bat 的变量可能无效,因为它可能不是蝙蝠。

【讨论】:

【参考方案2】:

baa 不是 b


 

您可以将Giraffe 类型的实例放入Animal 类型的变量中。 但是,您不能将Animal 的实例放入Giraffe 类型的变量中(如果它是Porcupine 怎么办?)

【讨论】:

【参考方案3】:

根据定义,派生类是可以做基类可以做的超集的东西。派生类可以做基类可以做的所有事情,但反之则不行。因此,将派生类视为基类是有意义的,反之则不然。例如:

class Animal 
    void eat();
;

class Dog : public Animal 
    void bark();

Dog 视为通用Animal 是完全合理的,但如果将通用Animal 订购到bark,则没有合理的做法。

【讨论】:

感谢@dssimcha 的解释【参考方案4】:

由于a 不是b,您不能将指向b 的指针分配给a 类型的对象。

由于ba,因此它确实可以正常工作。

【讨论】:

【参考方案5】:

ba 的一种。但是,a 不是b 的类型。如果b 包含其他成员,那么如果b 确实引用了a 类型的对象,那么当您尝试访问它们时会发生什么。

这不是一个安全的演员表。

【讨论】:

【参考方案6】:

请始终记住这一点,您不能将基类对象分配给派生类指针。 [派生类对象]是[基类对象]。 反之则不然。

【讨论】:

【参考方案7】:

问题在于“是”关系。 class a 的对象不是 class b 的对象所以

b *instance = new a(); // won't work

意味着您尝试将指向class b 的指针设置到不是对象class b 的对象上。同时你可以做相反的事情:

a* instance = new b(); //will work

因为class b 的对象也是class a 的对象,所以在这里您可以将指向class a 的指针设置到确实是class a 的对象上。

【讨论】:

【参考方案8】:

关于析构函数 -

如果操作数的静态类型与动态类型不同,则静态类型表现为基类,其析构函数必须是虚拟的。

此外,派生类的指针/引用与基类的指针/引用类型兼容。但其他情况并非如此。 (无传递性)

在你的情况下 -

a *instance = new b();  // This should be correct way.

instance 的静态类型为a*,而动态类型为b*。所以,a 作为一个基类,它的析构函数必须是virtual

【讨论】:

【参考方案9】:

您不能将a 替换为b,因为a 不具备b 的所有功能。因此,进行这样的分配是没有意义的。

对于第二部分,99% 的时间你说 delete var 来销毁分配给 new 的东西,而不是显式调用析构函数,这是一个高级主题。

【讨论】:

以上是关于为啥 C++ 不能用“超类”类型的右值初始化“派生类”类型的变量?的主要内容,如果未能解决你的问题,请参考以下文章

为啥定义复制构造函数会给我错误:不能将'obj&'类型的非常量左值引用绑定到'obj'类型的右值?

无法使用“const Node *”类型的右值初始化“Node *”类型的变量

重新理解C11的右值引用

从“A”类型的右值初始化“A&”类型的非常量引用无效[重复]

无法使用“value_type *”类型的右值(又名 char*)初始化“const unsigned char *”类型的参数

C11新特性右值引用&&