Перезапись чисто виртуальных функций путем «использования» отдельно унаследованного метода - PullRequest
8 голосов
/ 19 августа 2010

Просто небольшое раздражение, поскольку я могу обойти эту проблему, обернув производную функцию вместо использования ключевого слова 'using', но почему не работает следующее (компилятор говорит мне, что get_elem все еще чисто виртуальный вКласс «Бар»).

class Elem {};

class DerivedElem : public Elem {};

class Foo {

  public:
    virtual Elem&  get_elem() = 0;

};

class Goo {

  protected:
    DerivedElem elem;

  public:
    DerivedElem& get_elem()  { return elem; }

};


class Bar : public Foo, public Goo {

  public:
    using Goo::get_elem;

};

int main(void) {

  Bar bar;

}

Приветствия,

Том

Ответы [ 3 ]

5 голосов
/ 19 августа 2010

Если Goo - это «миксин», предназначенный для реализации интерфейса Foo особым образом (могут быть другие миксины с другими реализациями), то Goo может быть производным от Foo (вместо Bar это делается).

Если Goo не предназначен для реализации интерфейса Foo, то было бы ужасной ошибкой рассматривать Bar так, как если бы он реализовал эту чисто виртуальную функцию, когда на самом деле она просто имеет функцию с той же сигнатурой.Если вам нужны неявные интерфейсы и «утиная» типизация в C ++, вы можете сделать это, но вы должны сделать это с помощью шаблонов.Так или иначе, чисто виртуальные функции предназначены для явно объявленных интерфейсов, а функция Goo get_elem явно не объявлена ​​для реализации Foo::get_elem.Так что это не так.

Полагаю, это не объясняет, почему в принципе язык не может определить using Goo::get_elem for Foo; или какое-либо подобное объявление в Bar, чтобы избежать необходимости, чтобы Bar содержал многоОбразец обертывания вызова.

Вы можете, возможно, сделать что-то с шаблонами, чтобы позволить Goo поддержать это в некоторой степени, не зная по-настоящему о Foo:

template <typename T>
class Goo : public T {

  protected:
    DerivedElem elem;

  public:
    DerivedElem& get_elem()  { return elem; }
};

class Bar : public Goo<Foo> {};

class Baz : public Goo<Fuu> {};

Где Fuu это какой-то другойинтерфейс с функцией get_elem.Очевидно, что тогда ответственность автора Bar заключается в том, чтобы убедиться, что Goo действительно выполняет контракт Foo, и то же самое для Baz проверки контракта Fuu.

.Кстати, эта форма ковариации немного хитрая.Глядя на Foo, кто-то может ожидать, что выражение bar.get_elem() = Elem() будет действительным, а это не так, поэтому LSP нарушается.Ссылки такие забавные.((Foo &)bar).get_elem() = Elem() допустимо, но в целом не работает!Он присваивается только субобъекту Elem, и в этом отношении ((Foo &)bar).get_elem() = DerivedElem().Полиморфное присваивание в основном неприятность.

2 голосов
/ 19 августа 2010

В вашем примере, Foo и Goo являются отдельными классами. В Bar метод get_elem из Goo совсем не похож на метод в Foo, даже если их сигнатура совпадает.

Имея using Goo::get_elem, вы просто указываете компилятору разрешить неквалифицированный вызов get_elem () для вызова в Goo.

1 голос
/ 19 августа 2010

Вы столкнулись с одним из множества странных углов 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 *.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...