为什么&#34;模板<class t =“”> void f(T&amp; t)&#34;在T中覆盖函数但是&#34;模板<class t =“”> void f(T t)&#34;不能?

时间:2015-11-06 03:39:41

标签: c++ function templates object-slicing

我有一个带有子类B的A类,并希望创建一个模板函数来调用A和B的虚函数:

#include <stdio.h>
class A{
public:
    virtual void test(){
        printf("A\n");
    }
};
class B:public A{
public:
    virtual void test(){
        printf("B\n");
    }
};

template<class T>
void f(T t){
    t.test();
}
int main(){
    A* a=new B();
    f<A>(*a);
    return 0;
};

它仅打印A并且似乎不会覆盖test(),但是当我更改

void f(T t) 

void f(T& t) 
像那样:

#include <stdio.h>
class A{
public:
    virtual void test(){
        printf("A\n");
    }
};
class B:public A{
public:
    virtual void test(){
        printf("B\n");
    }
};

template<class T>
void f(T& t){
    t.test();
}
int main(){
    A* a=new B();
    f<A>(*a);
    return 0;
};

,它打印B,为什么会发生这种情况?

4 个答案:

答案 0 :(得分:1)

使用时

package program;

import java.awt.Dimension;
import java.awt.FlowLayout;

import javax.swing.DefaultListModel;
import javax.swing.JFrame; 
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;

public class Main{
public static void main(String[] args){

    JFrame frame = new JFrame();
    JPanel pane = new JPanel();
    DefaultListModel model = new DefaultListModel();
    JList list = new JList(model);

    //JFrame, frame
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(500, 500);
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);

    //JPanel, panel
    pane.setLayout(new FlowLayout());
    frame.add(pane);

    //JList, list

    String[] lists = {"asjd.txt", "okay.ss", "jsjs.okay.txt"};

    model.addElement(lists);

    JScrollPane listScroller = new JScrollPane(list);
    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    list.setLayoutOrientation(JList.VERTICAL);
    list.setVisibleRowCount(3);

    listScroller.setPreferredSize(new Dimension(250, 80));

    listScroller.setBounds(5, 5, 200, 300);
    pane.add(listScroller);




}
}

您的代码遭受对象切片的影响。您构造了template<class T> void f(T t){ ... } ,但只有对象的B部分传递给A

使用

时不会发生这种情况
f

答案 1 :(得分:0)

通过使用指针或参考来实现超越。请阅读一篇关于多态性的好文章。 快乐的编码。

答案 2 :(得分:0)

当你调用f(T t)时,c ++实际上是构造函数T(T& t)的新对象。然后将此对象的引用传递给函数。

你调用写代码来证明它

class A {
public:
    int x;
    A():x(6){
    }
    A(A& a) {
        x = 2;
    }
    virtual void test() {
        printf("%d\n", x);
    }
};
class B : public A {
public:
    virtual void test() {
        printf("%d\n", x);
    }
};

void fun(A a)
{
    a.test();
}

void f(A& a)
{
    a.test();
}
int main(void)
{
    A* a = new A();
    A* b = new B();
    A* c = new A(*b);
    fun(*a);
    fun(*b);
    f(*a);
    f(*b);
    f(*c);
}

输出为2 2 6 6 2

答案 3 :(得分:0)

如何定义类层次结构,类B派生自类A。因此,当调用类B's构造函数时,它必须首先调用A's构造函数,然后才能构造它。 A和&amp; B具有与test()相同的精确虚函数或定义。

首次实施f()时,您的模板将在编译时推断出参数类型。它正在寻找一个类类型,在main中,当您调用模板函数时,您告诉此模板函数需要class A类型。然后,这将使用A::test()来调用测试函数。在调用f()之前的main函数中,您正在动态创建类型为class A的指针并将其放在堆中,但是您正在使用B的构造函数,它是一个派生类型A。这将使用B's构造函数来调用A's构造函数。您的模板函数需要A类型,因此它会在您的代码中调用a.test()A::test()

f()的第二个声明或定义中,您的类定义相同,行为完全相同。这次有什么不同的是你的模板函数,它将在编译过程中解析,以推断出它的参数类型。在这个版本的函数中,它期望address of类类型A,因为它现在期望一个内存地址而不是实际变量本身,这次实例化或调用函数{{ 1}}它需要一个f()类型的参数,现在它正在使用c ++的引用功能,它现在调用T&

如果你想知道为什么会发生这种情况,请使用b.test()的第一个声明,并在f()设置断点并逐行进入代码,不要执行结束,但进入。然后使用第二版A* a = new B();执行相同的完整过程,您将看到发生了什么。

这不是因为班级正在或者没有覆盖虚拟功能;这是由于模板功能的工作原理。

现在我的问题是你为什么要为基类型创建一个指针,并通过调用它的派生类型的构造函数为它设置新内存。

通常使用多态和抽象类,您通常会创建一种派生类型,但是您可能拥有一个容器,该容器包含指向派生基类的指针,您通常会动态地将它们转换为派生类。例如,假设您有一个基类f(),它是抽象的;这意味着您无法直接创建此类,因为它的构造函数受到保护,只有派生类型才能访问它。现在,派生类型可能是AutomobileCarVanTruckSUVJeep以及其他类或函数已存储MotorCycle。因此,通过动态地将这些构造的对象指针指向其基类型vector<shared_ptr<Automobile>>,您可以将卡车,汽车和货车的智能指针全部推入同一容器中,因为它们都是公开继承的!但是,在使用抽象类型时,需要特别小心。

查看这个小程序,看看多态是如何工作的(这里没有抽象类型)

Automobile

<强>输出

#include <conio.h>
#include <string>
#include <iostream>
#include <vector>
#include <memory>

class Base {
public:
    Base() {}
    virtual ~Base(){}

    virtual void test() const { std::cout << "Base" << std::endl; } 
};

class DerivedA : public Base {
public:
    DerivedA() : Base() {}
    virtual ~DerivedA() {}

    virtual void test() const override { std::cout << "DerivedA" << std::endl; }
};

class DerivedB : public Base {
public:
    DerivedB() : Base() {}
    virtual ~DerivedB() {}

    virtual void test() const override { std::cout << "DerivedB" << std::endl; }
 };

template<class T>
void f( T t ) {
    t.test();
}

template<class T>
void g( T& t ) {
    t.test();
}

int main() {
    DerivedA* a = new DerivedA();
    //f<DerivedA>( *a );
    //g<DerivedA>( *a );

    DerivedB* b = new DerivedB();
    //f<DerivedB>( *b );
    //g<DerivedB>( *b );    

    std::vector<Base*> vBases;
    vBases.push_back( a );
    vBases.push_back( b );

    for ( unsigned i = 0; i < vBases.size(); ++i ) {
        if ( i == 0 ) {
            std::cout << "First Iteration: i = " << i << std::endl;
        } else if ( i == 1 ) {
            std::cout << "Second Iteration: i = " << i << std::endl;
        }

        f<DerivedA>( *dynamic_cast<DerivedA*>( vBases[i] ) );
        f<DerivedB>( *dynamic_cast<DerivedB*>( vBases[i] ) );

        std::cout << std::endl;

        g<DerivedA>( *static_cast<DerivedA*>( vBases[i] ) );
        g<DerivedB>( *static_cast<DerivedB*>( vBases[i] ) );

        std::cout << std::endl;
    }

    delete a;  // You Forgot To Delete Your Dynamic Pointers - Memory Leak!
    delete b;

    std::cout << "Press any key to quit" << std::endl;
    _getch();
    return 0;
}

也许这可以帮助您了解两种不同模板类型的情况,方法是使用DerivedA DerivedB DerivedA DerivedA DerivedA DerivedB DerivedB DerivedB dynamic_cast<>模板函数的第一个版本,并使用f<>()进行{static_cast<> 1}}用于模板函数的第二个版本,它接受引用而不是变量的堆栈副本。

如果你还记得这个向量中有两个元素,第一个是g<>(),第二个是DerivedA*,它们都是DerivedB*类型,存储为{{ 1}}在向量中。输出的前4行是仅在我们向量的第一个元素上完成的工作!输出的最后4行是仅在我们的向量的第二个元素上完成的工作!

我们在索引Base的第一个存储元素属于Base*类型,存储为0,第一次调用DerivedA我们动态地将其转换为Base*然后我们取消引用指针。第二次调用f<>()我们做同样的事情,除了我们动态地将它转换为DerivedA*类型并遵循它。所以这里第一个存储对象正在调用f<>(),然后它通过使用动态强制转换来调用DerivedB*

接下来的两行仍在处理同一个元素,即我们的DerivedA::test()在我们的向量中存储为DerivedB::test()的索引DerivedA*。这次我们现在使用Base*函数模板的第二种方法,而不是使用0我们现在使用的是g<>(),因为dynamic_cast<>期望引用一个对象而不是一个堆栈变量本身。如果你注意到这个时间周围什么都没有从一种类型转换到另一种类型,我们的函数模板总是调用static_cast<>,即使在第二次调用此方法时我们告诉它将它转换为g<>()类型。

对于接下来的4行输出,我们现在使用索引DerivedA::test()的向量中的第二个存储对象,但这次我们保存的对象是<DerivedB>类型,存储为{{1 }}。在接下来的两行中,我们具有与第一次迭代中相同的输出。第一次拨打1时,此DerivedBBase*被投放到DerivedB,第二次拨打DerivedA时保留了自己的类型,最后两行保留为f<>()可以看到存储为f<>()的{​​{1}}类型的索引1的存储对象在DerivedB的第一次调用时未被更改或转换为Base*类型{1}}。