2015-11-06 2 views
1

У меня есть класс А с классом ребенка B, и хочу сделать шаблон функцию для вызова виртуальной функции A и B:Почему «шаблон <class T> void f (T & t)» переопределяет функции в шаблоне T, но «<class 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; 
}; 

он печатает только и кажется, не отменяет тест(), но когда меняю

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, почему это произошло?

ответ

0

Превосходное достижение достигается с помощью указателей или ссылок. Пожалуйста, прочитайте хорошую статью о полиморфизме. Счастливое кодирование.

1

При использовании

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

ваш код страдает от объекта нарезания. Вы строите B, но только часть объекта A передается в f.

Это не происходит, когда вы используете

template<class T> 
void f(T& t){ ... } 
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

0

Как вы иерархия класс, определенный в том, что класс B является производным от класса A. Поэтому, когда вызывается конструктор класса B's, он должен сначала вызвать конструктор A's, прежде чем он сможет быть сконструирован. Оба A & B имеют ту же самую точную виртуальную функцию или определение, называемое test().

С вашей первой реализацией f() ваш шаблон будет выводить тип параметра во время его компиляции. Он ищет тип класса, в котором, когда вы вызываете функцию шаблона, вы говорите этой функции шаблона, ожидая тип class A. Затем он будет использовать A::test() для вызова тестовой функции. В своей основной функции перед вызовом f() вы создаете указатель типа class A динамически и помещаете его в кучу, но вы используете конструктор B, который является производным типом A. Это будет использовать конструктор B's для вызова конструктора A's. Функция шаблона ожидает тип A, поэтому он вызывает в вашем коде a.test() или A::test().

В вашей второй декларации или определении f() ваши классы определены одинаково и ведут себя точно так же.На этот раз отличается ваша функция шаблона, которая будет разрешена во время компиляции, чтобы вывести ее тип параметра. В этой версии функции ожидается address of класс типа A, так как теперь он ожидает адрес памяти в отличие от самой фактической переменной, на этот раз при создании или вызове функции f(), где он ожидает параметр типа T& теперь использует возможности ссылок C++, и теперь он вызывает b.test().

Если вы хотите узнать, почему это происходит, используйте свое первое объявление f() и установите точку останова, в которой у вас есть A* a = new B();, и входите в код по строкам, не переходите, а включайте. Затем сделайте точный точный процесс со своей второй версией f(), и вы увидите, что происходит.

Это не потому, что класс является или не переопределяет виртуальную функцию; это связано с тем, как работают шаблонные функции.

Теперь мой вопрос в том, почему вы хотите создать указатель для базового типа и установить для него новую память, вызвав его конструктор производного типа.

Обычно с полиморфизмом и абстрактными классами вы обычно создаете тип производного, но у вас может быть контейнер, содержащий указатели на базовый класс производных, в котором вы обычно динамически добавляете их. Например, скажем, что у вас есть базовый класс Automobile, который является абстрактным; что вы не можете создать этот класс напрямую, потому что он конструктор защищен, где доступ к нему могут получить только производные типы. Теперь производным типом может быть Car, Van, Truck, SUV, Jeep, 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<> для g<>() для ваша вторая версия вашей функции шаблона, которая берет ссылку вместо копии экземпляра переменной.

Если вы помните, есть два элемента в этом векторе первый будучи DerivedA* и второй является DerivedB* и которые относятся к типу Base и сохраняются как Base* в векторе. Первые 4 строки out put - это работа, которая выполняется только для первого элемента нашего вектора! Последние 4 строки out put - это работа, которая выполняется только для второго элемента нашего вектора!

Наш первый хранится элемент с индексом 0 имеет DerivedA типа хранится как Base* и первым вызовом f<>() мы динамически отбрасываемой его к DerivedA* типа, и мы затем разыменования указателя.Второй вызов f<>() мы делаем то же самое, за исключением того, что мы динамически применяем его к типу DerivedB* и уважаем его. Итак, здесь этот первый сохраненный объект вызывает DerivedA::test(), тогда он вызывает DerivedB::test() с помощью динамического приведения.

Следующие две строки все еще работают над одним и тем же элементом, который является нашим DerivedA*, хранящимся как Base* по индексу 0 в нашем векторе. На этот раз мы теперь используем g<>() ваш второй метод вашего шаблона функции, и вместо использования dynamic_cast<> мы теперь используем static_cast<>, так как g<>() ожидает ссылки на объект, а не на переменную стека. Если вы заметили, что на этот раз ничто не отбрасывается от одного типа к другому, и наш шаблон функции всегда вызывает DerivedA::test(), хотя при втором вызове этого метода мы говорим ему, чтобы он использовал его для типа <DerivedB>.

В течение следующих 4-х строк вывода теперь мы работаем с нашим 2 хранящегося объектом в векторе с индексом 1, но на этот раз наш сохраненный объект имеет DerivedB типа хранятся как Base*. На следующих двух строках мы имеем тот же результат, что и в первой итерации. На этот раз DerivedB отправляется на DerivedA для первого вызова f<>() и остается его собственным типом для второго вызова f<>() и для последних двух строк, поскольку вы можете видеть сохраненный объект с индексом 1, который имеет тип DerivedB, хранящийся как a Base* не изменяется или не сбрасывается до DerivedA при первом вызове g<>().

Смежные вопросы