Вы столкнулись с одним из множества странных углов C ++.В этом случае C ++ не считает две виртуальные функции, унаследованные от разных классов, одной и той же функцией, даже если они имеют одно и то же имя и сигнатуру типа.
Есть несколько веских причин для того, чтобы C ++ действовал таким образом.Например, часто бывает так, что эти две функции действительно не одинаковы, несмотря на то, что они имеют одинаковые имя и сигнатуру типа.Семантическое значение этих двух функций различно.
Вот пример:
namespace vendor1 {
class Circle {
public:
virtual ::std::size_t size() const { return sizeof(*this); }
};
} // namespace vendor1
namespace vendor2 {
class Shape {
public:
virtual double size() const = 0;
};
class Circle : public Shape {
public:
virtual double size() const { return radius_ * radius_ * M_PI; }
};
} // namespace vendor2
И тогда вы попробуете это:
namespace my_namespace {
class Circle : public ::vendor1::Circle, public ::vendor2::Circle {
// Oops, there is no good definition for size
};
Итак, вы должны прибегнуть кна это:
namespace my_namespace {
class Vendor1Circle : public ::vendor1::Circle {
public:
virtual ::std::size_t data_structure_size() const { return size(); }
};
class Vendor2Circle : public ::vendor2::Circle {
public:
virtual double area() const { return size(); }
};
class Circle : public Vendor1Circle, public Vendor2Circle {
// Now size is still ambiguous and should stay that way
// And in my opinion the compiler should issue a warning if you try
// to redefine it
};
Итак, у C ++ есть веские основания рассматривать виртуальные функции с одной и той же сигнатурой типа (возвращаемый тип не является частью сигнатуры типа) и именем из двух разных базисов как разными функциями.
Что касается using
... Все директивы using
гласят: «Добавьте имена из этого другого пространства имен в это пространство имен, как если бы они были здесь объявлены».Это пустое понятие в отношении виртуальных функций.Это просто говорит о том, что любая двусмысленность при использовании имени должна решаться другим способом.Он только объявляет имя, он не определяет имя.Для того, чтобы виртуальная функция была переопределена, требуется новое определение.
OTOH, если вы вставите простое встроенное переопределение thunk, например:
class Bar : public Foo, public Goo {
public:
virtual DerivedElem& get_elem() { return Goo::get_elem(); }
};
, хороший компилятор должен видеть, чтои знайте, что даже не удосужились создать функцию, а вместо этого просто возитесь с записями виртуальной таблицы, чтобы поступить правильно.Может потребоваться на самом деле выдать код для него и иметь символ, доступный в случае, если его адрес взят, но он все равно должен иметь возможность просто перевернуть виртуальную таблицу, чтобы функция полностью исчезла при вызове через Foo *
.