2010-03-13 8 views
1

Предположим, у меня есть это:Виртуальные функции и полиморфизм

class A 
{ 
    public: 
    virtual int hello(A a); 
}; 

class B : public A 
{ 
    public: 
    int hello(B b){ bla bla }; 
}; 

Таким образом, это абстрактный класс.

1) В классе B я определяю метод, который, по его мнению, переопределяет класс A. Но параметр немного отличается. Я не уверен в этом, это правильно? Возможно, из-за полиморфизма это нормально, но это довольно запутанно. 2) Если я сделаю: A a = новый B ;, а затем a.hello (lol); если «lol» это не тип B, тогда он даст ошибку компиляции ?, и если это тип A из другого класса C (класс C: public A), что бы произошло?

Я смущен о переопределении и виртуальной вещи. Все примеры, которые я нашел, работают с методами без параметров.

Любой ответ, ссылка или что-то еще ценное.

благодаря

PD: извините за мой английский

+2

Обратите внимание, что оператор 'new' возвращает указатель, поэтому' A a = new B; 'не соответствует правилу. Вы хотите либо «A a = B();», либо «A * a = new B();», хотя каждый делает разные вещи. Одно большое отличие - это бывшие фрагменты B (http://en.wikipedia.org/wiki/Object_slicing). Если A абстрактно, то только последнее является законным. – outis

+1

Чтобы иметь чистый виртуальный базовый класс, по крайней мере одна функция должна быть определена следующим образом: virtual int hello (A a) = 0; и все классы, полученные из этого базового класса mush, перезаписывают эту функцию. Вы не можете создать объект чистого виртуального базового класса. – TheFuzz

ответ

1

При переопределении метода, он переопределяет то, что этот метод будет делать. Вы можете только переопределять виртуальные члены, которые уже определены (с их набором параметров). Если тип есть A, будет вызван метод на A. Если тип B, то метод на B будет вызываться, даже если переменная набирается A, но содержит экземпляр типа B.

Вы не можете изменить определения параметров для переопределенного метода, иначе это будет перестают быть переопределением.

3

Во-первых, A не является абстрактным классом в вашем коде. У него должна быть хотя бы одна чистая виртуальная функция абстрактной.

  1. различные параметры означают совершенно другой метод, даже если имя то же самое. Думайте об этом как о другом имени. Вот почему это называется «подпись». Если A будет абстрактным классом, этот код вообще не будет компилироваться.

  2. A :: hello() будет называться. Нет проблем с этим, и параметр должен быть типом A, как если бы не было наследования.

+0

Я никогда не определял привет в классе A, так что функции являются чисто виртуальными, следовательно, является абстрактным классом. (Заметим, что я объявил и определил привет в B). – ritmbo

+5

@ritmbo: нет, это неверно. Чтобы объявить чистую виртуальную функцию, вы должны написать 'int hello (A a) = 0;'. То, что вы сделали, объявляет функцию, но никогда не определяло ее. Это совсем не то же самое. –

1

Что вы делаете там перегрузки не перекрывая, т.е. это как если класс B является:

class B 
{ 
public: 
    int hello(A a) {...} 
    int hello(B b) {...} 
}; 

У вас есть две функции одного и того же имени, но с разными подписями, что делает их различные функции (как и стандартная библиотека, имеют разные функции для abs(float) и abs(double) и т. д.)

Если вы хотите переопределить, то вам нужно иметь такую ​​же подпись, то есть cla Привет ss B должен принять параметр типа A. Таким образом, когда вы вызываете hello на объект класса B, он будет использовать класс B hello, а не класс A.

Если вы действительно хотите, чтобы приветствие класса B принимало только объекты типа B, то то, что у вас есть, хорошо, хотя вы, вероятно, хотите, чтобы класс A hello был не виртуальным, поскольку вы на самом деле не хотите его переопределять - вы определение новой функции с новыми параметрами и новым поведением.

+0

Если 'A' происходит от' B', не будет ли вызов «привет» двусмысленным в глазах компилятора? –

+0

@ Zach, нет, он использует наиболее производный. Обратите внимание, что я предполагаю, что ошибка при прохождении значения фиксирована (не может передавать абстрактные классы по значению). –

+2

Это не перегрузка, потому что обе декларации функций находятся в разных областях. 'B :: hello' * скрывает * привет в' A'. В вашем примере это перегрузка только. –

7

Ваш класс B не переопределения функции члена в А, это перегрузках его. Или, во всяком случае, старайтесь, см. Бит о скрытии позже.

Overriding - это когда производный класс определяет собственную версию виртуальной функции-члена из базового класса. Перегрузка - это когда вы определяете разные функции с тем же именем.

Когда виртуальный вызов выполняется по указателю или ссылке, имеющей тип базового класса, он будет только «рассматривать» переопределения в производном классе, а не в перегрузках. Это важно - для того, чтобы экземпляр B обрабатывался вызывающими программами, как будто он делает все, что может сделать A (что является точкой динамического полиморфизма и виртуальных функций), его функция hello должна иметь возможность принимать любой объект типа A Функция hello, которая принимает объекты типа B, а не любые A, является более ограничительной. Он не может играть роль функции A hello, поэтому это не переопределение.

Если вы немного экспериментируете с вызовом hello на A и B, передавая объекты типа A или B, вы должны иметь возможность увидеть разницу. A имеет функцию, принимающую A (которую вы не определили, поэтому, если вы ее назовете, ваша программа не сможет ссылаться, но вы можете это исправить). B имеет функцию, берущую B. Они имеют одно и то же имя, и, разумеется, поскольку B происходит от A, вы можете передать B в функцию, берущую функцию A. Но B не действует как переопределение в виртуальных вызовах ,

Функцию A можно использовать для объекта B, но только через ссылку или указатель на A. Особенность C++ заключается в том, что определение hello в B скрывает определение в A. Если перегрузка - это то, что вы хотите , можно отменить функцию базового класса, добавив using A::hello; в класс B. Если переопределение - это то, что вы хотите, вы должны определить функцию, принимающую те же параметры. Например:

#include <iostream> 

class A 
{ 
    public: 
    virtual int hello(A a) {std::cout << "A\n"; } 
    virtual int foo(int i) { std::cout << "A::Foo " << i << "\n"; } 
}; 

class B : public A 
{ 
    public: 
    using A::hello; 
    // here's an overload 
    int hello(B b){ std::cout << "B\n"; }; 
    // here's an override: 
    virtual int foo(int i) { std::cout << "B::Foo " << i << "\n"; } 
}; 

int main() { 
    A a; 
    B b; 
    a.hello(a); // calls the function exactly as defined in A 
    a.hello(b); // B "is an" A, so this is allowed and slices the parameter 
    b.hello(a); // OK, but only because of `using` 
    b.hello(b); // calls the function exactly as defined in B 
    A &ab = b; // a reference to a B object, but as an A 
    ab.hello(a); // calls the function in A 
    ab.hello(b); // *also* calls the function in A, proving B has not overridden it 
    a.foo(1); // calls the function in A 
    b.foo(2); // calls the function in B 
    ab.foo(3); // calls the function in B, because it is overridden 
} 

Выход:

A 
A 
A 
B 
A 
A 
A::Foo 1 
B::Foo 2 
B::Foo 3 

Если Вы убираете using A::hello; линию от B, то вызов b.hello(a); не удается скомпилировать:

error: no matching function for call to `B::hello(A&)' 
note: candidates are: int B::hello(B) 
1

Thansk за ответы, но Я должен прояснить некоторые вещи, чтобы получить окончательный ответ.

Предположим, у меня есть класс A, как я определил его в исходном вопросе. И я добавить еще один метод:

class A { 
    ... 
    int yeah(); 
} 

Тогда я определить класс B, как:

class B : public A { 
    int hello(A a); 
}; 

И другой класс C, аналогичной B.

То, что я знаю, потому что я программист , то, что методы hello B и C будут иметь объекты типа A в качестве параметров, но экземпляры одного и того же класса. В примере:

B b; 
b.hello(some_other_b_instance); 

или

C c; 
c.hello(some_other_c_instance); 

Проблема в том, что в каждой здравствуйте функции классов В и С, я хочу делать определенные вещи с atributes конкретного класса B или C. И из-за параметра есть тип A, я не могу их использовать.

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

Надеюсь, у вас есть идея кода ... Класса абстрактна, и реальная работа имеет смысл в конкретных кланах B и C, каждый из которых выполняет свою работу определенным образом, чтобы заставить функцию yeah работать , Но B и C должны получить доступ к своим членам для правильной работы приветствия.

+0

Я нашел ответ на свой вопрос. DYNAMIC_CAST !!!!!! – ritmbo

+0

Разъяснения к вопросу должны затрагивать вопрос, а не размещаться в качестве ответа. SO - это сайт Q & A, а не форум. – outis

+0

Если вы объявляете 'B :: hello' как' int hello (A a) 'и передаете экземпляр B, экземпляр будет подвергаться разрезанию и станет A (что-то особенное для B будет отброшено); 'dynamic_cast' не поможет. Перейдите по ссылке const (или используйте указатель, но рекомендуется ссылка на константу), чтобы предотвратить нарезку: 'int B :: hello (const A & a)'. – outis

4

Куча хороших ответов, говорящих вам, что происходит, я думал, что я буду прыгать с ПОЧЕМУ.

Там эта вещь называется Liskov Substitution Principle, который говорит, что функция в подклассе должен работать под тем же preconditions и postconditions в качестве базового класса. В этом случае функция должна иметь возможность работать с любым объектом типа A. Обратите внимание, что из-за отношений наследования каждый B есть-a A, но не каждый A есть-a B. Итак, чтобы заменить базовый метод, новая функция в производном классе может ослабить предпосылки или усилить постусловия, но не укрепить предпосылки или ослабить постусловия.

Ваша попытка переопределить усиливает предварительное условие, он принимает Bs, а не все As.

Отметьте, что covariance IS допускается по типам возврата. Если ваш базовый класс возвратил A, то он гарантирует, что возвращаемое значение равно -A. Тогда базовый класс может вернуть B, потому что каждый B есть-a A.

Но для входных параметров только контравариантность соответствует теоретическому требования параметров LSP и in/out являются инвариантами. В C++, в частности, все типы параметров являются инвариантными для целей перегрузки.

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