Это нормально, но я хочу, чтобы он был как чистый. Как я могу это сделать? Возможно ли это?
Ответ 2
Ну, я должен дать неожиданный ответ здесь! Денникрайн сказал, что вы можете это сделать:
virtual int CompareTo(IComparableObject const &obj)=0;
но это неверно. О да, он компилируется, но бесполезен, потому что он никогда не может быть реализован правильно.
Эта проблема имеет основополагающее значение для краха (статически типизированной) Ориентации объектов, поэтому очень важно, чтобы программисты, использующие OO, распознавали проблему. Проблема имеет имя, она называется проблемой ковариации, и она полностью разрушает OO как общую парадигму программирования; то есть способ представления и самостоятельного выполнения общих абстракций.
Это объяснение будет немного длинным и неряшливым, так что несите меня и пытайтесь читать между строками.
Во-первых, абстрактный класс с чистым виртуальным методом без аргументов может быть легко реализован в любом производном классе, поскольку метод имеет доступ к нестационарным переменным данных производного класса через этот указатель. Этот указатель имеет тип указателя на производный класс, и поэтому мы можем сказать, что изменяется вместе с классом, на самом деле это ковариационный с производным типом класса,
Позвольте мне назвать этот тип полиморфизма первым порядком, он явно поддерживает отправку предикатов по типу объекта. Действительно, возвращаемый тип такого метода также может варьироваться в зависимости от типа объекта и класса, т.е. Тип возврата ковариационный.
Теперь я обобщу идею метода без аргументов, чтобы разрешить произвольные скалярные аргументы (такие как int), утверждая, что это ничего не меняет: это всего лишь семейство методов, индексированных скалярным типом. Важное свойство здесь состоит в том, что скалярный тип замкнут. В производном классе должен использоваться точно такой же скалярный тип. другими словами, тип инвариант.
Общее введение инвариантных параметров в виртуальную функцию по-прежнему допускает полиморфизм, но результат остается в первом порядке.
К сожалению, такие функции имеют ограниченную полезность, хотя они очень полезны, когда абстракция только в первом порядке: хорошим примером являются драйверы устройств.
Но что, если вы хотите смоделировать что-то интересное, то есть это хотя бы отношение?
Ответ на этот вопрос: вы не можете этого сделать. Это математический факт и не имеет никакого отношения к языку программирования. Предположим, у вас есть абстракция для слов, чисел, и вы хотите добавить один номер к другому номеру или сравнить их (как в примере OP). Игнорируя симметрию, если у вас есть N реализаций, вам нужно будет написать N ^ 2 функции для выполнения операций. Если вы добавляете новую реализацию абстракции, вам нужно написать N + 1 новые функции.
Теперь у меня есть первое доказательство того, что OO завинчивается: вы не можете поместить N ^ 2 методов в виртуальную схему отправки, потому что такая схема является линейной. N классов дает вам N методов, которые вы можете реализовать, и для N > 1, N ^ 2 > N, поэтому OO вкручивается, QED.
В контексте С++ вы можете увидеть проблему: подумайте:
struct MyComparable : IComparableObject {
int CompareTo(IComparableObject &other) { .. }
};
Arggg! Мы влипли! Мы не можем заполнить здесь часть ..
, потому что у нас есть только ссылка на абстракцию, которая не имеет данных для сравнения. Конечно, этот должен, потому что существует открытое/неопределенное/бесконечное количество возможных реализаций. Нет возможного способа написать одну процедуру сравнения как аксиому.
Конечно, если у вас есть различные подпрограммы свойств или общее универсальное представление, вы можете это сделать, но это не учитывается, потому что тогда отображение универсального представления без параметров и, следовательно, абстракция - это только первый порядок. Например, если у вас есть различные целочисленные представления, и вы добавляете их, преобразовывая оба в тип данных GNU gmp mpz, тогда вы используете две ковариантные проекционные функции и одну глобальную неполиморфную функцию сравнения или добавления.
Это не встречный пример, это не решение проблемы, которое должно представлять собой отношение или метод, ковариантный по крайней мере в двух переменных (по крайней мере, сам по себе и других).
Вы можете подумать, что можете решить это с помощью:
struct MyComparable : IComparableObject {
int CompareTo(MyComparable &other) { .. }
};
В конце концов вы можете реализовать этот интерфейс, потому что теперь вы знаете представление другого, так как оно является MyComparable.
Не смейтесь над этим решением, потому что это именно то, что сделал Бертранд Майер в Эйфеле, и это то, что многие люди делают на С++ с небольшим изменением, чтобы попытаться обойти тот факт, что он не безопасен для типа, t фактически переопределяет функцию базового класса:
struct MyComparable : IComparableObject {
int CompareTo(IComparableObject &other) {
try
MyComparable &sibling = dynamic_cast(other);
...
catch (..) { return 0; }
}
};
Это не решение. В нем говорится, что две вещи не равны только потому, что они имеют разные представления. Это не соответствует требованию, которое заключается в том, чтобы сравнить две вещи в абстрактном. Например, два числа не могут быть равны только потому, что используемое представление различно: нуль равен нулю, даже если один является mpz, а другой - int. Помните: идея состоит в том, чтобы правильно представлять абстракцию, а это значит, что поведение должно зависеть только от абстрактного значения, а не от деталей конкретной реализации.
Некоторые люди попробовали двойную отправку. Ясно, что это тоже не сработает. Здесь нет возможности избежать основной проблемы: вы не можете заполнить квадрат в строке.
виртуальная отправка функций линейна, проблемы второго порядка квадратичны, поэтому OO не может представлять проблемы второго порядка.
Теперь я хочу быть очень ясным, что С++ и другие статически типизированные языки OO сломаны, не, потому что они не могут решить эту проблему, потому что они не могут быть решены, и это не проблема: ее простой факт. Причина, по которой эти языки и парадигма OO в целом нарушены, заключается в том, что они обещают, чтобы предоставить общие абстракции, а затем не могут этого сделать. В случае С++ это обещание:
struct IComparableObject { virtual int CompareTo(IComparableObject obj)=0; };
и вот где неявный контракт сломан:
struct MyComparable : IComparableObject {
int CompareTo(IComparableObject &other) { throw 00; }
};
потому что реализация, которую я дал там, фактически является единственно возможной.
Хорошо, прежде чем уйти, вы можете спросить: что такое правильный путь (TM).
Ответ: используйте функциональное программирование. В С++ это означает шаблоны.
template<class T, class U> int compare(T,U);
Итак, если у вас есть N типов для сравнения, и вы действительно сравниваете все комбинации, то да, действительно, вам нужно предоставить N ^ 2 специализации. Что показывает шаблоны доставлять по обещанию, по крайней мере в этом отношении. Это факт: вы не можете отправлять во время выполнения по открытым наборам типов, если функция является вариантом более чем в одном параметре.
BTW: на случай, если вы не уверены в теории.. просто взгляните на стандартную библиотеку ISO С++ и посмотрите, сколько там полиморфизма виртуальных функций по сравнению с функциональным программированием с шаблонами.
Наконец, обратите внимание на то, что я не говорю, что классы и такие вроде бесполезны. Я сам использую полиморфизм виртуальных функций: я говорю, что это ограничивается конкретными проблемами, а не общим способом представления абстракций и, следовательно, недостойным называемой парадигмой.