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

时间:2017-09-28 04:32:15

标签: c++ pointers inheritance

查看答案Why do we need virtual functions in C++,它们表明虚拟函数允许后期绑定,这样可以在下传时调用子类函数。

对于completness,我包含有问题的代码。

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."

我的问题是:

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

4 个答案:

答案 0 :(得分:2)

  

我怎样才能这样做,接收参数(不是指针)到子类实例的函数将使用overriden方法?换句话说,func2怎么会导致“我正在吃老鼠。”。

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

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

然后,使用

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

将按照您的希望工作。

  

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

这是由对象切片引起的。请参阅What is object slicing?以了解对象切片是什么以及它如何在您的代码中发挥作用。

答案 1 :(得分:2)

C ++中的整个虚函数机制基于选择要调用的实际函数的想法,该函数基于 dynamic (&#34; actual&#34;)类型的对象使用呼叫。在这个功能

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

xyz对象的类型为Animal。它被明确硬编码为Animal。它的静态类型是Animal。其动态类型为Animal。在所有方面都是Animal。你自己要求编译器通过使用上面的参数声明来实现它。

这意味着xyz.eat();来电始终会致电Animal::eat()。没有办法绕过它。由于此处不涉及Cat::eat()对象,因此无法拨打Cat。通过传递Cat作为实际参数,您只需要求编译器从Animal xyz生成Cat,然后忽略原始Cat。 (这通常被称为&#34;切片&#34;。)所有后续工作都是使用Animal xyz完成的。

答案 2 :(得分:1)

解释是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

答案 3 :(得分:0)

您可以使用值语义来实现您的类。然后你不需要指针或引用。请参阅Sean Parent的“Better Code: Runtime Polymorphism”,这可能会让您大吃一惊。 Here's the code。观看演讲并了解他如何使用值语义来避免错误的数据共享并实现令人印象深刻的多态撤消系统。这是一个带注释的样本:

// 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 上查看它。

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

另请参阅描述此方法的blog post