代码之家  ›  专栏  ›  技术社区  ›  LeastSquaresWonderer

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

  •  2
  • LeastSquaresWonderer  · 技术社区  · 7 年前

    看看答案 Why do we need virtual functions in 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."
    

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

    我的问题是:

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

    4 回复  |  直到 7 年前
        1
  •  2
  •   R Sahu    7 年前

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

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

    然后,使用

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

    会像你希望的那样工作。

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

    这是由对象切片引起的。看见 What is object slicing?

        2
  •  2
  •   AnT stands with Russia    7 年前

    C++中的整个虚拟函数机制都是基于以下思想:根据 动态 该调用中使用的对象的(“实际”)类型。在此函数中

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

    类型 xyz Animal 动物 动物 动物 动物 在各个方面。您自己要求编译器使用上述参数声明来实现。

    xyz.eat(); Animal::eat() . 这是没有办法的。没有办法打电话 Cat::eat() 因为没有 Cat 此处涉及的对象。通过传递 作为实际参数,您只需要求编译器生成一个 Animal xyz 从那以后 . (这通常被称为“切片”。)所有后续工作均由 动物xyz .

        3
  •  1
  •   CiaPan    7 年前

    func2 作用 没有 使用传递给它的对象。函数有自己的参数变量 Animal xyx 这就是 从您的 animal cat 变量所以在

    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
    
        4
  •  0
  •   metal    7 年前

    可以使用值语义实现类。那么你就不需要指针或引用了。请参阅“ 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