Существует много сообщений, жалующихся на перегрузку оператора.
я чувствовал, что должен был разъяснить "оператор, перегружающий" понятия, предложив альтернативную точку зрения на это понятие.
Этим аргументом является ошибка.
столь же легко запутать код в C или Java через функции/методы, как это находится в C++ посредством перегрузок оператора:
// C++
T operator + (const T & a, const T & b) // add ?
{
T c ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
// Java
static T add (T a, T b) // add ?
{
T c = new T() ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
/* C */
T add (T a, T b) /* add ? */
{
T c ;
c.value = a.value - b.value ; /* subtract !!! */
return c ;
}
Для другого примера, давайте посмотрим Cloneable
интерфейс в Java:
Вы, как предполагается, клонируете объект, реализовывая этот интерфейс. Но Вы могли лгать. И создайте другой объект. На самом деле этот интерфейс так слаб, Вы могли возвратить другой тип объекта в целом, просто ради удовольствия:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Как эти Cloneable
интерфейс может быть оскорблен/запутан, он должен быть запрещен на том же основании, перегрузка оператора C++, как предполагается?
Мы могли перегрузиться toString()
метод MyComplexNumber
, класс, чтобы иметь его возвращает stringified час дня. toString()
перегрузка должна быть запрещена, также? Мы могли саботировать MyComplexNumber.equals
, чтобы иметь его, возвращают случайное значение, изменяют операнды... и т.д. и т.д. и т.д.
В Java, как в C++, или безотносительно языка, программист должен уважать минимум семантики когда написание кода. Это означает реализовывать add
функция, которая добавляет, и Cloneable
метод внедрения, который клонируется, и ++
оператор, чем инкременты.
Теперь, когда мы знаем, что код может саботироваться даже через нетронутые методы Java, мы можем спросить нас о реальном использовании оператора, перегружающегося в C++?
Мы выдержим сравнение ниже, для различных случаев, "того же" кода в Java и C++, чтобы иметь идею, от которой вид стиля кодирования более свободен.
// C++ comparison for built-ins and user-defined types
bool isEqual = A == B ;
bool isNotEqual = A != B ;
bool isLesser = A < B ;
bool isLesserOrEqual = A <= B ;
// Java comparison for user-defined types
boolean isEqual = A.equals(B) ;
boolean isNotEqual = ! A.equals(B) ;
boolean isLesser = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual = A.comparesTo(B) <= 0 ;
Обратите внимание на то, что A и B мог иметь любой тип в C++, пока перегрузки оператора обеспечиваются. В Java, когда A и B не являются примитивами, код может стать очень сбивающим с толку, даже для подобных примитиву объектов (BigInteger, и т.д.)...
// C++ container accessors, more natural
value = myArray[25] ; // subscript operator
value = myVector[25] ; // subscript operator
value = myString[25] ; // subscript operator
value = myMap["25"] ; // subscript operator
myArray[25] = value ; // subscript operator
myVector[25] = value ; // subscript operator
myString[25] = value ; // subscript operator
myMap["25"] = value ; // subscript operator
// Java container accessors, each one has its special notation
value = myArray[25] ; // subscript operator
value = myVector.get(25) ; // method get
value = myString.charAt(25) ; // method charAt
value = myMap.get("25") ; // method get
myArray[25] = value ; // subscript operator
myVector.set(25, value) ; // method set
myMap.put("25", value) ; // method put
В Java, мы видим, что, чтобы каждый контейнер сделал то же самое (получают доступ к его содержанию через индекс или идентификатор), у нас есть различный способ сделать это, который сбивает с толку.
В C++, каждый контейнер использует тот же способ получить доступ к его содержанию благодаря перегрузке оператора.
примеры ниже используют Matrix
объект, найденный использованием первых ссылок, найденных на Google для" объект Матрицы Java " и" объект Матрицы C++ ":
// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E = A * (B / 2) ;
E += (A - B) * (C + D) ;
F = E ; // deep copy of the matrix
// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ; // deep copy of the matrix
И это не ограничено матрицами. BigInteger
и BigDecimal
классы Java страдают от того же запутывающего многословия, тогда как их эквиваленты в C++ так же ясны как встроенные типы.
// C++ Random Access iterators
++it ; // move to the next item
--it ; // move to the previous item
it += 5 ; // move to the next 5th item (random access)
value = *it ; // gets the value of the current item
*it = 3.1415 ; // sets the value 3.1415 to the current item
(*it).foo() ; // call method foo() of the current item
// Java ListIterator "bi-directional" iterators
value = it.next() ; // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ; // sets the value 3.1415 to the current item
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
// C++ stream handling (with the << operator)
stringStream << "Hello " << 25 << " World" ;
fileStream << "Hello " << 25 << " World" ;
outputStream << "Hello " << 25 << " World" ;
networkStream << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;
// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;
хорошо, в Java можно использовать MyString = "Hello " + 25 + " World" ;
также... Но, ожидайте секунда: Это - перегрузка оператора, не так ли? Разве это не обман???
:-D
тот же общий код, изменяющий операнды, должен быть применимым оба для built-ins/primitives (которые не имеют никаких интерфейсов в Java), стандартные объекты (который не мог иметь правильного интерфейса), и пользовательские объекты.
, Например, вычисляя среднее значение двух значений произвольных типов:
// C++ primitive/advanced types
template
T getAverage(const T & p_lhs, const T & p_rhs)
{
return (p_lhs + p_rhs) / 2 ;
}
int intValue = getAverage(25, 42) ;
double doubleValue = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix
// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.
Теперь, когда мы видели справедливые сравнения между перегрузкой оператора использования кода C++ и тем же кодом в Java, мы можем теперь обсудить "оператор, перегружающийся" как понятие.
Даже за пределами информатики, существует перегрузка оператора: Например, в математике, операторы как [1 122], -
, *
, и т.д. перегружаются.
Действительно, значение [1 125], -
, *
, и т.д. изменяется в зависимости от типов операндов (численные данные, векторы, квантовые волновые функции, матрицы, и т.д.).
Большинство из нас, как часть наших научных курсов, изучило несколько значений для операторов, в зависимости от типов операндов. Мы находили их сбивающими с толку, их?
, Это - самая важная часть перегрузки оператора: Как в математике, или в физике, операция зависит от типов своих операндов.
Так, знайте тип операнда, и Вы будете знать эффект операции.
В C, реальное поведение оператора изменится согласно его операндам. Например, добавление двух целых чисел отличается, чем добавление два удваивается, или даже одно целое число и одно двойное. Существует даже целый домен адресной арифметики с указателями (без кастинга, можно добавить к указателю целое число, но Вы не можете добавить два указателя...).
В Java, нет никакой адресной арифметики с указателями, но кто-то все еще нашел конкатенацию строк без +
, оператор будет достаточно смешон выровнять по ширине исключение в "перегрузке оператора, злое" кредо.
Это просто, что Вы, как C (по историческим причинам) или Java (для [1 195] личные мотивы , видят ниже), кодер, Вы не можете обеспечить свое собственное.
В C++, оператор, перегружающийся для встроенных типов, не возможен (и это - хорошая вещь), но пользовательский , типы могут иметь пользовательский перегрузки оператора.
, Как уже сказано ранее, в C++, и об обратном к Java, пользовательские типы не считают второразрядными гражданами языка, по сравнению со встроенными типами. Так, если встроенные типы имеют операторы, пользовательские типы должны смочь иметь их, также.
истина - то, что, как toString()
, clone()
, equals()
методы для Java ( т.е. квазиподобный стандарту ), перегрузка оператора C++ является таким большим количеством части C++, что это становится столь же естественным как исходные операторы C, или перед упомянутыми методами Java.
Объединенный с шаблонным программированием, перегрузка оператора становится известным шаблоном разработки. На самом деле Вы не можете пойти очень далеко в STL, не используя перегруженные операторы, и перегрузив операторы для Вашего собственного класса.
, перегрузка Оператора должна стремиться уважать семантику оператора. Не вычитайте в +
оператор (поскольку в "не вычитают в add
, функция", или "возвращают дерьмо в clone
метод").
перегрузка Броска может быть очень опасной, потому что они могут привести к неоднозначностям. Таким образом, они должны действительно быть зарезервированы для четко определенных случаев. Что касается [1 135] и ||
, никогда не перегружайте их, если Вы действительно не знаете то, что Вы делаете, поскольку Вы потеряете оценку короткого замыкания, которой обладают собственные операторы &&
и ||
.
, поскольку James Gosling сказал так:
я не учел оператор, перегружающийся как довольно личный выбор , потому что я видел, что слишком много людей злоупотребили им в C++.
James Gosling. Источник: http://www.gotw.ca/publications/c_family_interview.htm
сравните текст Gosling выше с Stroustrup ниже:
Много проектных решений C++ имеют свои корни в моей неприязни к тому, чтобы вынуждать людей делать вещи некоторым конкретным способом [...] Часто, я испытал желание запретить функцию, которую я лично не любил, я воздержался от выполнения поэтому, потому что я не думал, что имел право вызвать мои представления о других .
Bjarne Stroustrup. Источник: Desing и Эволюция C++ (1.3 Общих Фона)
Некоторые объекты значительно извлекли бы выгоду из перегрузки оператора (конкретные или числовые типы, как BigDecimal, комплексные числа, матрицы, контейнеры, итераторы, компараторы, синтаксические анализаторы и т.д.).
В C++, можно получить прибыль от этого преимущества из-за смирения Stroustrup. В Java Вы просто завинчены из-за Gosling личный выбор .
причинами того, что не был добавлен оператор, перегружающийся теперь в Java, могло быть соединение внутренней политики, аллергии на функцию, недоверие к разработчикам (Вы знаете, саботажника, которые, кажется, преследуют команды Java...), совместимость с предыдущим JVMs, время для записи корректной спецификации, и т.д.
Так не задерживают Ваше дыхание, ожидающее этой функции...
Да...
, В то время как это далеко от того, чтобы быть единственной разницей между этими двумя языками, этот развлекает меня.
, По-видимому, люди C#, с их "каждый примитив struct
, и struct
происходит из Объекта" , разобрался в нем при первой попытке.
Несмотря на весь FUD против используемой определенной перегрузки оператора, следующие языки поддерживают его: Scala, Dart, Python , F# , C#, D, Алгол 68 , Smalltalk, Groovy, Perl 6, C++, Ruby, Haskell, MATLAB, Eiffel, Lua, Clojure, Фортран 90 , Swift, Ada, Delphi 2005 ...
Столько языков, с так многими отличающимися (и иногда выступающий) основные положения, и все же они все приходят к соглашению по тому вопросу.
Пища для размышления...
В следующем примечании из документации MSDN на ThreadPool
сказано все:
Потоки в пуле управляемых потоков являются фоновыми потоками. То есть их свойства
IsBackground
истинны. Это означает, что потокThreadPool
не будет поддерживать работу приложения после выхода всех потоков переднего плана .
Ваше приложение просто завершает работу (по достижении конца Main
) до того, как ваши потоки закончат работу.
Как насчет коротких и сладких?
static int wrkThreads = 0;
static readonly EventWaitHandle exit = new ManualResetEvent(false);
static readonly object syncLock = new object();
static void Main( string[] items )
{
wrkThreads = items.Length;
foreach ( var item in items )
ThreadPool.QueueUserWorkItem(( DoWork ), item);
exit.WaitOne();
}
static void DoWork( object obj )
{
lock ( syncLock ) {
/* Do your file work here */
}
if ( Interlocked.Decrement(ref wrkThreads) == 0 )
exit.Set();
}
Это простая версия того, о чем я говорил. Он использует одно событие и не опрашивает и не вращает, и он написан таким образом, чтобы его можно было использовать повторно, а также позволять несколько рабочих наборов одновременно. Лямбда-выражения могут быть исключены, если это более удобно для отладки.
class Program
{
static void Main(string[] args)
{
var items = new string[] { "1", "2", "3", "300" };
using (var outfile = File.AppendText("file.txt"))
{
using (var ws = new WorkSet<string>(x =>
{ lock (outfile) outfile.WriteLine(x); }))
foreach (var item in items)
ws.Process(item);
}
}
public class WorkSet<T> : IDisposable
{
#region Interface
public WorkSet(Action<T> action)
{ _action = action; }
public void Process(T item)
{
Interlocked.Increment(ref _workItems);
ThreadPool.QueueUserWorkItem(o =>
{ try { _action((T)o); } finally { Done(); } }, item);
}
#endregion
#region Advanced
public bool Done()
{
if (Interlocked.Decrement(ref _workItems) != 0)
return false;
_finished.Set();
return true;
}
public ManualResetEvent Finished
{ get { return _finished; } }
#endregion
#region IDisposable
public void Dispose()
{
Done();
_finished.WaitOne();
}
#endregion
#region Fields
readonly Action<T> _action;
readonly ManualResetEvent _finished = new ManualResetEvent(false);
int _workItems = 1;
#endregion
}
}