纯粹的虚拟C++奇怪的行为
Weird behaviour C++ pure virtuals
纯虚拟类C++怪异行为
请帮助血腥的C++初学者更好地理解纯虚拟课程。
我尝试了一个带有C++虚拟的简单示例,但不确定结果。 例如,如果我在另一种编程语言中尝试相同的方法,例如,输出将是
期望/预期输出
1 -> 推文
2 -> 推文
但是,这里的输出是
实际输出
1 -> 喵
2 -> 推特
为什么? 似乎类动物的运算符=没有效果。 这是因为类动物的标准运算符=被称为什么都不做吗? 如何在不使用指针的情况下实现类似于 java 的行为? 这可能吗?
下面是一个尽可能简化的代码示例:
法典
#include <string>
#include <iostream>
using namespace std;
class Animal
{
public:
virtual string test() const = 0;
};
class Cat : public Animal
{
public:
virtual string test() const
{
return "Meow";
}
};
class Bird : public Animal
{
public:
virtual string test() const
{
return "Tweet";
}
};
void test_method(Animal &a)
{
Bird b;
a = b;
cout << "1 -> " << a.test() << endl;
cout << "2 -> " << b.test() << endl;
}
int main(int args, char** argv)
{
Cat c;
Animal &a = c;
test_method(a);
return 0;
}
显然,您对a = b
作业的行为有一些不切实际的期望。
您的a = b
赋值只是将数据从Bird
对象的子对象Animal
b
复制到a
引用Cat
对象的子对象Animal
。由于Animal
根本没有数据,因此a = b
是一个空操作,一个无操作。它根本不做任何事情。
但即使它确实做了一些事情,它也只会复制数据字段。它无法复制对象的多态标识。无法更改C++中对象的多态标识。无法更改对象的类型。无论您做什么,Cat
将始终保持Cat
。
您编写的代码可以缩短为
Cat a;
Bird b;
(Animal &) a = b; // Same as `(Animal &) a = (Animal &) b`
a.test(); // still a `Cat`
b.test(); // still a `Bird`
你以一种更模糊的方式做了同样的事情。
在C++引用不能被回弹;它们总是引用你创建它们的变量。在这种情况下,a
是对c
的引用,因此它是并且将永远是一个Cat
。
重新分配引用时,不会重新绑定引用 - 而是在分配引用引用的基础变量。因此,赋值a = b
与c = b
相同。
您是所谓的对象切片的受害者。C++ 在处理对象的方式上与 Java 和 C# 不同。例如,在 C# 中,当您创建新实例时,每个用户定义类型都作为引用进行管理,并且只有基元类型和结构作为值进行处理,这意味着在 Java 或 C# 中,当您分配对象时,您只分配引用,例如:
对象 a = 新对象(); 对象 b = a;
将导致 a 和 b 都指向同一个对象(我们分配 a 时创建的那个)。
在C++,故事是不同的。 您可以在堆或堆栈中创建对象的实例。 并且您可以通过引用、指针或值传递所述对象。
如果你分配引用或指针,它的行为将类似于C#和Java。但是,如果按值分配对象,即分配实际对象而不是指针或引用,则将创建该对象的新副本。默认情况下,每个用户在C++中定义类型都是可复制的。
当涉及继承和多态性时,此复制行为会产生问题,因为将子类型复制到父类型时,将创建的副本将仅包含子类型中父类型的部分信息,从而丢失您可能具有的任何多态性。
在您的示例中,当您将 Cat 对象复制到 Animal 对象时,只复制 cat 的 Animal 部分,这就是您失去多态性的原因,虚拟表不再存在。如果你的基类以任何方式是抽象的,这甚至是不可能的。
如果要保留多态性,解决方案是通过指针或引用而不是按值传递对象。您可以在堆中创建对象并分配该指针,您可以在堆栈中获取对象的地址并断言该指针,或者您可以只获取对象的引用并分配它。
这里要吸取的教训是,永远不要通过具有任何类型的多态性的值对象传递或赋值,否则您最终会对其进行切片。
试试这个。
#include <string>
#include <iostream>
using namespace std;
class Animal
{
public:
virtual string test() const = 0;
};
class Cat : public Animal
{
public:
virtual string test() const
{
return "Meow";
}
};
class Bird : public Animal
{
public:
virtual string test() const
{
return "Tweet";
}
};
void test_method(Animal *a)
{
Bird *b = new Bird();
a = b;
cout << "1 -> " << a->test() << endl;
cout << "2 -> " << b->test() << endl;
free(a);
}
int main(int args, char** argv)
{
Cat *c = new Cat();
Animal *a = c;
test_method(a);
free(c);
return 0;
}
删除a = b
,你会得到"喵",然后是"推文"...
以更通用的态度:
首先,为泛型类定义一个泛型接口(在此特定示例中Animal
)。
然后,您可以将此接口与任何子类实例一起使用(在此特定示例中为Cat
和Bird
)。
每个实例都将根据您的特定实现"操作"。
您在函数test_method
中的错误是使用了子类的实例,而没有通过泛型类(带有引用或指针)引用它。
为了将其更改为Animal
实例的通用函数,您可以执行以下操作:
void test_method(Animal &a)
{
cout << a.test() << endl;
}
- 虚拟决赛作为安全
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- 如何在C++中获得"静态纯虚拟"功能?
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 大小虚拟继承中的派生类
- 链接器找不到在虚拟类 c++ 中访问的静态字段的符号
- 使用 C++ 和 i2c 工具从虚拟 i2c 写入和读取
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- std::is_trivially_copyable_v 关于虚拟功能
- 删除C++继承中虚拟类成员的代码重复
- 子类地址等于虚拟基类地址?
- 当覆盖存在时调用基本虚拟"binded to object"函数
- 用于创建/注册虚拟存储设备的 IOKit 驱动程序
- 是否可以使用基类非虚拟方法中的派生类虚拟方法?