繁体   English   中英

不处理指针时,调用子类(虚拟)函数(后期绑定)

[英]Calling subclass (virtual) function (late binding), when not dealing with pointers

查看答案, 为什么我们需要C ++中的虚函数,它们表明虚函数允许后期绑定,从而在向下转换时可以调用子类函数。

为了完整起见,我加入了相关代码。

class Animal
{
 public:
   virtual void eat() { 
       std::cout << "I'm eating generic food.";
   }
};
class Cat : public Animal
{
public:
  void eat() { std::cout << "I'm eating a rat."; }
};

现在考虑以下两个功能

void func(Animal *xyz) { xyz->eat(); }
void func2(Animal xyz) { xyz.eat(); }

我们看到调用func导致

Animal *animal = new Animal;
Cat *cat = new Cat;
func(animal); // outputs: "I'm eating generic food."
func(cat);    // outputs: "I'm eating a rat."

在调用func2时导致

Animal animal;
Cat cat;
func2(animal); // outputs: "I'm eating generic food."
func2(cat);    // outputs: "I'm eating generic food."

我的问题是:

我如何做到这一点,使接收参数(而不是指向子类实例的指针)的函数将使用重写的方法? 换句话说,func2如何导致“我在吃老鼠”。 此外,我想了解为什么会出现这种差异。 我提前谢谢你。

我如何做到这一点,使接收参数(而不是指向子类实例的指针)的函数将使用重写的方法? 换句话说,func2如何导致“我在吃老鼠”。

您可以使用引用而不是指针。

void func2(Animal& xyz) { xyz.eat(); }

然后,使用

Animal animal;
Cat cat;
func2(animal);
func2(cat); 

将按照您的期望工作。

此外,我想了解为什么会出现这种差异。

这是由对象切片引起的。 请参阅什么是对象切片? 了解什么是对象切片以及它在代码中的作用。

C ++中的整个虚拟函数机制均基于以下想法:根据该调用中使用的对象的动态 (“实际”)类型选择要调用的实际函数。 在此功能

void func2(Animal xyz) { xyz.eat(); }

xyz对象的类型是Animal 它明确地硬编码为Animal 它的静态类型是Animal 它的动态类型是Animal 各个方面都是Animal 您自己要求编译器使用上述参数声明来做到这一点。

这意味着xyz.eat(); 调用将始终调用Animal::eat() 没有办法解决。 由于没有涉及Cat对象,因此无法将其称为Cat::eat() 通过将Cat作为实际参数传递,您只是在要求编译器从该Cat生成Animal xyz ,然后忽略原始Cat (这通常被称为“切片”。)所有后续工作都是使用Animal xyz完成的。

原因是func2函数不适用于您传递给它的对象。 该函数具有自己的参数变量Animal xyx ,该变量是根据您的animalcat变量构造的 因此,在func2内部,您始终有一个要处理的基类对象,因此该函数始终会调用通用答案。

void func2(Animal xyz)  // uses its own Animal xyz, created on call
{
    xyz.eat();          // uses a local xyz object of Animal class with its generic functions
}

Cat cat;
func2(cat);       // here a new Animal xyz is created from the cat

您必须将指向您实际对象的指针或引用作为函数的参数传递,以允许函数访问对象的特定重写虚拟函数。 像这样:

void func2(Animal& xyz)   // uses a reference to the argument object
{
    xyz.eat();            // the actual argument is used with its overridden functions
}

Cat cat;
func2(cat);               // the cat is passed to the callee

您可以使用值语义实现类。 然后,您不需要指针或引用。 请参阅Sean Parent的“ 更好的代码:运行时多态 ”,这可能会让您大吃一惊。 这是代码 观看演讲,了解他如何使用值语义来避免不良数据共享并实现令人印象深刻的多态撤消系统。 这是一个带注释的示例:

// A generic draw function that any type that streams to std::ostream can use
template <typename T>
void draw(const T& x, ostream& out, size_t position)
{ 
  out << string(position, ' ') << x << endl; 
}

// A class that can hold anything with value semantics
// -- note: no virtual functions and no inheritance!
class object_t {
    // ... see the talk for the details here ...
    // This is where the magic happens
};

// Define a vector of our object_t to be a document
using document_t = vector<object_t>;

// Overload the draw() function for document - just iterate
// through the vector and call draw() on each item in it
void draw(const document_t& x, ostream& out, size_t position)
{
    out << string(position, ' ') << "<document>" << endl;
    for (auto& e : x) draw(e, out, position + 2);
    out << string(position, ' ') << "</document>" << endl;
}

// Define my own class that the code above knows nothing 
// about and that doesn't inherit from object_t
class my_class_t {
    /* ... */
};

// Overload draw for it
void draw(const my_class_t&, ostream& out, size_t position)
{ out << string(position, ' ') << "my_class_t" << endl; }

// Use all this stuff
int main()
{
    document_t document; // just a vector!

    // Add some objects that don't inherit from object_t!
    document.emplace_back(0);
    document.emplace_back(string("Hello!"));

    // Show what we've got so far
    draw(document, cout, 0);

    // Add some more stuff
    document.emplace_back(document); // Add a whole copy of the current doc!
    document.emplace_back(my_class_t()); // Add an arbitrary type that doesn't inherit from object_t!

    draw(document, cout, 0);
}

打印:

<document>
  0
  Hello!
</document>
<document>
  0
  Hello!
  <document>
    0
    Hello!
  </document>
  my_class_t
</document>

看到它在Wandbox上运行。

再看一遍-您只是获得了多态行为,而不必显式继承自基类。 (提示:他使用重载作为主要机制,而不是继承。)

另请参阅此博客文章,介绍了这种方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM