Почему не там никакая контравариантность параметра для переопределения?

Это пути к файлам, не так ли? Рассмотрите возможность использования File.getName (), особенно если у вас уже есть объект File:

File file = new File("core/pages/viewemployee.jsff");
String name = file.getName(); // --> "viewemployee.jsff"

И удалить расширение:

String res = name.split("\\.[^\\.]*$")[0]; // --> "viewemployee"

С помощью этого мы можем обрабатывать строки типа "../viewemployee.2.jsff".

Регулярное выражение соответствует последней точке, ноль или больше нетонов и конец строки. Затем String.split () рассматривает их как разделитель и игнорирует их. Массив всегда будет иметь один элемент, если только исходная строка ..

26
задан Oak 8 June 2010 в 08:55
поделиться

6 ответов

Чистый вопрос контра-дисперсии

Добавление контра-дисперсии к языку открывает множество потенциальных проблем или нечистых решений и дает очень мало преимуществ, поскольку его можно легко смоделировать без языковая поддержка:

struct A {};
struct B : A {};
struct C {
   virtual void f( B& );
};
struct D : C {
   virtual void f( A& );     // this would be contravariance, but not supported
   virtual void f( B& b ) {  // [0] manually dispatch and simulate contravariance
      D::f( static_cast<A&>(b) );
   }
};

Простым дополнительным прыжком вы можете вручную преодолеть проблему языка, который не поддерживает противоречивость. В этом примере f (A &) не обязательно должен быть виртуальным, и вызов полностью квалифицирован, чтобы запретить виртуальный механизм диспетчеризации.

Этот подход показывает одну из первых проблем, которые возникают при добавлении контравариантности к языку, который не имеет полной динамической диспетчеризации:

// assuming that contravariance was supported:
struct P {
   virtual f( B& ); 
};
struct Q : P {
   virtual f( A& );
};
struct R : Q {
   virtual f( ??? & );
};

При действующей контравариантности Q :: f будет переопределение P :: f , и это будет хорошо, как и для каждого объекта o , который может быть аргументом P :: f , того же объекта является допустимым аргументом для Q :: f . Теперь, добавляя дополнительный уровень к иерархии, мы сталкиваемся с проблемой проектирования: является ли R :: f (B &) допустимым переопределением P :: f или должно быть R :: f (A &) ?

Без контравариантности R :: f (B &) явно является переопределением P :: f , поскольку подпись является идеальным совпадением.Как только вы добавите контравариантность к промежуточному уровню, проблема в том, что есть аргументы, которые действительны на уровне Q , но не находятся ни на уровне P , ни на R . Для R , чтобы выполнить требования Q , единственный выбор - заставить подпись быть R :: f (A &) , чтобы следующий код мог компилироваться :

int main() {
   A a; R r;
   Q & q = r;
   q.f(a);
}

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

struct R : Q {
   void f( B& );    // override of Q::f, which is an override of P::f
   virtual f( A& ); // I can add this
};

Теперь у нас есть забавный эффект:

int main() {
  R r;
  P & p = r;
  B b;
  r.f( b ); // [1] calls R::f( B& )
  p.f( b ); // [2] calls R::f( A& )
}

В [1] есть прямой вызов метода-члена R . Поскольку r является локальным объектом, а не ссылкой или указателем, не существует механизма динамической диспетчеризации, и наилучшим соответствием является R :: f (B &) . В то же время, в [2] вызов выполняется через ссылку на базовый класс, и срабатывает виртуальный механизм диспетчеризации.

Поскольку R :: f (A &) является переопределением Q :: f (A &) , который, в свою очередь, является заменой P :: f (B &) , компилятор должен вызвать R :: f (A &) . Хотя это можно точно определить на языке, было бы удивительно узнать, что два почти точных вызова [1] и [2] на самом деле вызывают разные методы, и что в [2] система вызовет not наилучшее совпадение аргументов.

Конечно, можно утверждать иначе: R :: f (B &) должно быть правильным переопределением, а не R :: f (A &) .Проблема в этом случае:

int main() {
   A a; R r;
   Q & q = r;
   q.f( a );  // should this compile? what should it do?
}

Если вы проверите класс Q , предыдущий код будет совершенно правильным: Q :: f принимает A & как аргумент. У компилятора нет причин жаловаться на этот код. Но проблема в том, что при этом последнем предположении R :: f принимает в качестве аргумента B & , а не A & ! Фактически существующее переопределение не сможет обработать аргумент a , даже если подпись метода в месте вызова кажется совершенно правильной. Этот путь приводит нас к выводу, что второй путь намного хуже первого. R :: f (B &) не может быть заменой Q :: f (A &) .

Следуя принципу наименьшего удивления, как для разработчика компилятора, так и для программиста намного проще не иметь противоречивых аргументов в аргументах функций. Не потому, что это невозможно, а потому, что в коде будут странности и сюрпризы, а также с учетом того, что существуют простые обходные пути, если функция отсутствует в языке.

При перегрузке и сокрытии

И в Java, и в C ++, в первом примере (с A , B , C и D ) удаление отправки вручную [0], C :: f и D :: f - это разные подписи и не отменяют.В обоих случаях они фактически являются перегрузками с одним и тем же именем функции с той небольшой разницей, что из-за правил поиска C ++ перегрузка C :: f будет скрыта с помощью D :: f . Но это только означает, что компилятор не найдет перегрузку hidden по умолчанию, а не то, что ее нет:

int main() {
   D d; B b;
   d.f( b );    // D::f( A& )
   d.C::f( b ); // C::f( B& )
}

И с небольшим изменением определения класса можно заставить работать точно так же как в Java:

struct D : C {
   using C::f;           // Bring all overloads of `f` in `C` into scope here
   virtual void f( A& );
};
int main() {
   D d; B b;
   d.f( b );  // C::f( B& ) since it is a better match than D::f( A& )
}
24
ответ дан 28 November 2019 в 07:06
поделиться
class A {
    public void f(String s) {...}
    public void f(Integer i) {...}
}

class B extends A {
    public void f(Object o) {...} // Which A.f should this override?
}
15
ответ дан Don Roby 28 November 2019 в 07:06
поделиться

Хотя это приятно иметь на любом оо-языке, мне все еще нужно столкнуться с его применимостью в моей нынешней работе.

Может быть, в этом нет нужды.

3
ответ дан xtofl 28 November 2019 в 07:06
поделиться

Благодаря ответам Донроби и Дэвида, я думаю, что я понимаю, что основная проблема с введением контрастной дисперсии параметра - интеграция с механизмом перегрузки .

Таким образом, проблема не только с одним переопределением для нескольких методов, но и с другой стороны:

class A {
    public void f(String s) {...}
}

class B extends A {
    public void f(String s) {...} // this can override A.f
    public void f(Object o) {...} // with contra-variance, so can this!
}

И теперь есть два допустимых переопределения для одного и того же метода:

A a = new B();
a.f(); // which f is called?

Кроме проблем с перегрузкой, я не мог думать ни о чем другом.

Редактировать: С тех пор я нашел эту запись C ++ FQA (20.8) , которая согласуется с вышесказанным - наличие перегрузки создает серьезную проблему для противоречивости параметров.

1
ответ дан Oak 28 November 2019 в 07:06
поделиться

Для C++ Строуструп кратко обсуждает причины скрытия в разделе 3.5.3 книги The Design & Evolution of C++. Его аргументация такова (я перефразирую), что другие решения поднимают столько же проблем, и так было со времен C With Classes.

В качестве примера он приводит два класса - и производный класс B. Оба имеют виртуальную функцию copy(), которая принимает указатель соответствующих типов. Если мы скажем:

A a;
B b;
b.copy( & a );

то в настоящее время это ошибка, так как copy() класса B скрывает copy() класса A. Если бы это не было ошибкой, то только части A из B могли бы обновляться функцией copy() из A.

Еще раз, я перефразировал - если вам интересно, прочтите книгу, она превосходна.

5
ответ дан 28 November 2019 в 07:06
поделиться

Спасибо Донроби за его ответ выше - я просто расширяю его.

interface Alpha
interface Beta
interface Gamma extends Alpha, Beta
class A {
    public void f(Alpha a)
    public void f(Beta b)
}
class B extends A {
    public void f(Object o) {
        super.f(o); // What happens when o implements Gamma?
    }
}

Вы сталкиваетесь с проблемой, похожей на причину, по которой не рекомендуется множественное наследование реализаций. (Если вы попытаетесь вызвать A.f (g) напрямую, вы получите ошибку компиляции.)

2
ответ дан 28 November 2019 в 07:06
поделиться