Почему я не могу использовать наследование для реализации интерфейса на C++?

Возможный дубликат:
Реализация абстрактного класса члены родительского класса
Почему C++ не позволяет базовым классам реализовывать унаследованный интерфейс производного класса?

Учитывая эти объекты:

struct A
{
    virtual void foo() = 0;
};

struct B
{
    void foo() { /* neat implementation */ }
};

Интересно, почему с точки зрения компилятора следующий объект считается абстрактным:

struct C : B, A
{
    using B::foo; // I tried my best to make the compiler happy
};

Компилятор не позволит мне сделать это:

A* a = new C;

Visual Studio 2010 говорит:

'C': невозможно создать экземпляр абстрактного класса из-за следующих членов: 'void A::foo(void)': является абстрактным: см. объявление 'A::foo'

г++ 4.6.3 говорит:

не может выделить объект абстрактного типа «C», потому что следующие виртуальные функции являются чистыми в «C»: virtual void A::foo()

Я предполагаю, что это разрешено в С#, но я не знаю задействованных механизмов - мне просто любопытно.


person mister why    schedule 10.05.2012    source источник
comment
Самое близкое, что вы получите, вероятно, что-то вроде template<class Base> struct B: Base{ void foo() {...} };, а затем struct C: B<A> {};.   -  person leftaroundabout    schedule 10.05.2012
comment
Еще один дубликат: stackoverflow.com/questions/3291568/   -  person Sjoerd    schedule 10.05.2012


Ответы (4)


struct B реализует функцию, которая называется foo и не принимает аргументов. Эта функция явно не имеет никакого отношения к A::foo. Итак, когда struct C происходит от обоих A и B, он в конечном итоге унаследовал:

  • ответственность за реализацию для A::foo
  • метод B::foo, который имеет то же имя и сигнатуру, что и A::foo, но никак с ним не связан

Думаю проблема очевидна. Вы пытаетесь использовать A как эквивалент C# interface, но нет способа выразить эту концепцию в C++.

Директива using здесь тоже не поможет, потому что все, что она делает, это переводит B::foo в область действия C, а это означает, что она сообщает компилятору, что он должен рассматривать B::foo как кандидата для разрешения имени foo, когда последнее встречается внутри класса C. К сожалению, это также не связано с ответственностью за реализацию чисто виртуальных методов.

person Jon    schedule 10.05.2012
comment
можно утверждать, что using явно соединит их. Это не так (фактически), но могло бы. Это, конечно, желаемое за действительное. - person Matthieu M.; 10.05.2012
comment
@MatthieuM. - Вы случайно не знаете, является ли это сознательным дизайнерским решением не придавать using этому значению здесь, или просто никто никогда не предлагал этого? - person Flexo; 10.05.2012
comment
@MatthieuM.: Извините, редактирую. Я думаю, что текущий ответ также охватывает это. - person Jon; 10.05.2012
comment
@MatthieuM.: Может, не значит, что должен. - person SigTerm; 10.05.2012
comment
@awoodland: я не знаю. Я бы хотел, чтобы C++ имел лучшую поддержку делегирования (в частности, для атрибутов, что-то вроде using v::insert было бы здорово, чтобы избежать поведения производных для наследования, которое мы так часто видим); с другой стороны, это всего лишь пара строк, так что это в основном синтаксический сахар. - person Matthieu M.; 10.05.2012
comment
@MatthieuM.: Синтаксический сахар может иметь монументальное значение. Что бы вы предпочли: встроенные лямбда-выражения или несколько десятков одноразовых классов в базе кода? Я настолько избалован превосходным лямбда-синтаксическим сахаром C#, что считаю C++03 варварским по сравнению с ним. - person Jon; 10.05.2012
comment
@Jon: я не говорил, что это бесполезно. Эй, лично я предпочитаю в C++11 конструкцию range-for! Может пора предложить, делегирование интересно (практически); однако, что хорошо с синтаксическим сахаром, так это то, что мы можем работать в то же время с минимальной суетой. Я предпочитаю эмулировать делегирование с помощью инлайнеров, чем эмулировать семантику перемещения с помощью auto_ptr ;) - person Matthieu M.; 10.05.2012
comment
@MatthieuM.: Согласен. Просто в 2012 году, и в основном из-за отличной работы команды C#, мне грустно, когда компилятор вынуждает меня проходить чистую волокиту (хотя надо признать, что у комитета по стандартам C++ ужасно меньше свободы действий, чем у MS с C#). ). - person Jon; 10.05.2012

К сожалению, это не совсем то, что вы хотите.

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

Дело в том, что C++ не поддерживает делегирование напрямую. Есть только небольшой поворот, заключающийся в явном включении имен в область видимости.

person Matthieu M.    schedule 10.05.2012

Чтобы решить эту проблему, вам нужно реализовать свою собственную foo() в C и явно вызвать функцию B, эффективно перенаправив A::foo в B::foo внутри C:

struct C : B, A
{
  virtual void foo() {
    // Override A::foo by forwarding to B::foo:
    B::foo();
  }
};

На каком-то уровне это имеет смысл — между A::foo() и B::foo() нет неотъемлемого отношения — вы должны явно установить его внутри C.

person Drew Hall    schedule 10.05.2012

Я думаю, это разрешено в С#

С++ это не С#.

virtual void foo() и void foo() — это два разных метода.

Когда вы наследуете как A, так и B, вы получаете доступ к двум методам: virtual A::foo и невиртуальному B::foo. И virtual A::foo абстрактно. Отсюда и ошибка компилятора.

person SigTerm    schedule 10.05.2012