Russian Qt Forum
Май 03, 2024, 21:12 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 ... 6 7 [8]   Вниз
  Печать  
Автор Тема: C++ vs D  (Прочитано 67216 раз)
BRE
Гость
« Ответ #105 : Октябрь 31, 2008, 18:49 »

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

Тем, что управлением памятью я могу управлять так, как считаю нужным и для разных объектов могу сделать разные механизмы.
Как в С++.

Тем, что когда я достаю значение по указателю, то достается значение из ячейки памяти, без кучи дополнительных проверок, а все ли там правильно. Если мне это потребуется я могу это сделать сам, так как мне нужно или использовать готовое решение.
Как в С++.

Если мне нужны строки, я не заморачиваюсь что Сишная библиотека использует char *, С++ std::string а Qt - QString, я просто использую строки.
Какой функционал у строк в D? Он хоть немного приближается к QString? Если нет, а хочется, подключать стороннии решения?


В чем ключевые преимущества перед С++?
Записан
Eldar
Гость
« Ответ #106 : Ноябрь 01, 2008, 06:16 »

Плюсы язык слишком высокого уровня. Надо напрягать моск ассемблером.
Записан
Eldar
Гость
« Ответ #107 : Ноябрь 04, 2008, 00:12 »

Код:
я, например, так и не услышал ответа на многочисленные вопросы касаемо делегатов в Д...
Ну могу ответить. Вопрос был вроде - можно ли методы класса вызывать по имени в D. Можно. И если постараться - то все, а не только обработанные специальным препроцессором(я про moc). Только вопрос - зачем. Все методы-слоты известны на этапе компиляции, динамически добавлять методы нельзя. Зачем эта рюшечка в виде invokeMethod() ?
Можно также создавать объекты по имени класса, это реализуется доп-библиотекой и работает без доступа к исходному коду.
Записан
Eugene Efremov
Гость
« Ответ #108 : Ноябрь 28, 2008, 02:52 »

Я попробую сравнить некоторые особенности этих языков со своей колокольни. На конкретном примере. При этом я постараюсь сделать его максимально похожим на обоих языках (например, вывод в обоих случаях через printf, хотя и в C++, и в D есть более специфические средства).

Итак, вот программа на C++, ничего особо осмысленного не делающая, но, при этом, иллюстрирующая некоторые весьма раздражающие особенности этого языка (комментарии вида /* N */ — сноски, поясняются ниже):

Код
C++ (Qt)
#include <stdio.h>
 
class base
{
private:
int i, j;
 
void prn(int t)
{
printf("%s: %d \n", pref(), t); /* 1 */
}
 
protected:
void prn_i() { prn(i); }
void prn_j() { prn(j); }
 
public:
base(int a, int b) : i(a), j(b) {}   /* 2 */
base(int c) : i(c+1), j(c+2) {}   /* 2 */
 
virtual const char* pref()
{
return "bs";
}
};
 
 
class iface   /* 4 */
{
public:
 
virtual void foo() = 0;
virtual void bar() = 0;
void all()
{
foo();
bar();
}
};
 
 
class impl1 : public base, public iface
{
public: // base
impl1(int a, int b) : base(a, b) {}   /* 3 */
impl1(int c) : base(c) {}   /* 3 */
 
virtual const char* pref()
{
return "i1";
}
 
public: // iface   /* 4 */
virtual void foo() { prn_i(); }
virtual void bar() { prn_j(); }
};
 
 
class impl2 : public base, public iface
{
public: // base
impl2(int a, int b) : base(a, b) {}   /* 3 */
impl2(int c) : base(c) {}   /* 3 */
 
virtual const char* pref()
{
return "i2";
}
 
public: // iface   /* 4 */
virtual void foo() { prn_j(); }
virtual void bar() { prn_i(); }
};
 
 
 
void poly(iface& i)   /* 5 */
{
i.all();
}
 
int main()
{
impl1 i1(10);
impl2 i2(20);
poly(i1);
poly(i2);
return 0;
}
 

Итак, что мы имеем:

/* 1 */ — ф-ция pref возвращает указатель. Предполагается, что это указатель на строку, и что она оканчивается нулем. И здесь это действительно так. Однако никто не мешает мне в производном классе написать такую реализацию pref, которая будет возвращать черт знает что. Например — NULL. С закономерным результатом, в виде падения всего этого дела. В C++ нет такой вещи, как встроенная строка...

/* 2 */ — списки инициализаторов в конструкторах. Весьма негибкий механизм. Здесь мы из-за него имеем "всего лишь" дублирование кода. В более сложных случаях нам, например, могли бы потребоваться некие достаточно сложные промежуточные вычисления — и мы не смогли бы их осуществить без отказа от использования этих списков. Если инициализируется достаточно сложный объект — такой отказ неприемлем...

/* 3 */ — если мы хотим, чтобы производный класс сохранял свой интерфейс, мы вынуждены дублировать в нем все конструкторы базового класса. Избежать дублирования кода невозможно.

/* 4 */ — множественное наследование. Сама по себе вещь может и хорошая, но реализована в C++ совершенно ужасным образом. Чего стоит один только механизм вызова конструктора у виртуальных базовых классов, когда мы вынуждены их вызывать на протяжении всей иерархии! Про многочисленные неоднозначности, которые регулярно возникают в результате использования всего этого дела, вообще молчу... (Здесь, впрочем, всего этого нет — я, все-таки, хотел, чтобы программа компилировалась...)

/* 5 */ — объект передается по ссылке. И это надо указывать явно. Пожалуй это больше плюс, чем минус. По крайней мере, ясно видно, где что. (Да, знаю, что здесь нужен const... Но для примера и так сойдет.)


А теперь посмотрим, как это будет на D:

Код
D
import std.stdio;
 
class base
{
private:
int i, j;
 
void prn(int t)
{
printf("%*s: %d \n", pref(), t);  /* 1 */
}
 
protected:
void prn_i() { prn(i); }
void prn_j() { prn(j); }
 
public:
this(int a, int b)   /* 2 */
{
i = a;
j = b;
}
this(int c)   /* 2 */
{
this(c+1, c+2);
}
 
char[] pref()
{
return "bs";
}
}
 
 
interface iface   /* 4 */
{
void foo();
void bar();
void all();
 
template iface_mix()
{
void all()
{
foo();
bar();
}
}
}
 
 
class impl1 : base, iface
{
// base:
this(int a, int b) { super(a,b); }   /* 3 */
this(int c) { super(c); }   /* 3 */
 
char[] pref()
{
return "i1";
}
 
// iface   /* 4 */
mixin iface_mix;
 
void foo() { prn_i(); }
void bar() { prn_j(); }
}
 
 
class impl2 : base, iface
{
// base:
this(int a, int b) { super(a,b); }   /* 3 */
this(int c) { super(c); }   /* 3 */
 
char[] pref()
{
return "i2";
}
 
// iface   /* 4 */
mixin iface_mix;
 
void foo() { prn_j(); }
void bar() { prn_i(); }
}
 
 
void poly(iface i)   /* 5 */
{
i.all();
}
 
int main()
{
impl1 i1 = new impl1(10);   /* 6 */
impl2 i2 = new impl2(20);   /* 6 */
poly(i1);
poly(i2);
return 0;
}
 

Итак, что изменилось и что добавилось:

/* 1 */ — ф-ция pref возвращает именно строку. Заданного размера. Завершающий ноль не нужен. Если я в каком-то из производных классов не стану ее инициализировать — поле размера станет нулем и printf не выведет на ее месте ничего.

/* 2 */ — списки инициализаторов отменены. Мы инициализируем значения в конструкторах путем обычного присваивания. Более того, мы можем вызывать конструкторы друг из друга, избегая, таким образом дублирования кода.

/* 3 */проблема не решена. Мы по-прежнему вынуждены дублировать здесь код. Может быть, конечно, дело в том, что я не слишком хорошо знаю этот язык, но способа избежать этого я не вижу...  Грустный

/* 4 */ — от проблем, вызванных множественным наследованием, здесь решили избавится радикально: множественное наследование запрещено. D, в этом, увы, не одинок. Печальный опыт кривой реализации этой фичи в C++ еще долго будет давать о себе знать... Но D предлагает на замену такой механизм, как миксины. И разрешает использовать их внутри интерфейсов. Это позволяет реализовать почти все, что можно реализовать с помощью множественного наследования, избегая, вроде бы, граблей последнего. Здесь только одна проблема: при таком подходе все миксины будут разворачиваются в область видимости имплементации. Соответственно, если миксинов много — опять-таки может возникнуть конфликт имен. И я не уверен, что разрешить его будет легче, чем в C++...

/* 5 */ — объект класса передается по ссылке. Всегда. Если нужно передавать по значению — это должна быть структура, а не класс. Я не уверен, что это хорошо. Конечно, в 99% процентах случаев нам требуется именно такое поведение, но если потребуется обратное — у нас проблемы...

/* 6 */ — а вот и кое-что новое. Сборка мусора. Палочка о двух концах. Конечно, это удобно. Очень удобно. Но, с другой стороны — это сильные тормоза и потенциальный memory leak из-за "мертвых ссылок". Лучше было бы сделать эту фичу более опциональной...


И того. Что мы имеем только из одного этого примера? Из 4 продемонстрированных проблем C++, 2 решены, 1 решена частично, 1 не решена вообще. Плюс появилось 2 новые фичи, и по крайней мере 1 из них вполне может создать новые проблемы.

Конечно, это очень небольшая часть языка. Я не коснулся шаблонов (где в C++ очень много проблем, а в D  — изменений). Я не рассматривал такие нововведения, как поддержка многопоточности, контрактное программирование, отложенные вычисления... Я не упомянул о других проблемах (например, неоднозначность выражений, подобных i=i++ + ++i в D никуда не делась...).

Но в целом этот небольшой пример достаточно хорошо демонстрирует общее положение вещей. D позицирует себя как язык, обладающий возможностями C++, но не страдающий его проблемами. Мы же здесь ясно видим: некоторые (пусть не слишком важные и редко используемые, но тем не менее) возможности потеряны, задача же избавления от унаследованных от C++ проблем решена чуть более, чем наполовину.


Вывод: Четверка. С минусом. И не более.
Во всяком случае — пока...
Записан
Tonal
Гость
« Ответ #109 : Ноябрь 28, 2008, 11:17 »

1. Из функции pref нужно возвращать std::string или константную ссылку на неё char* следует оставлять только для связи с внешним API.

2. Если нужны какие-то дополнительные сложные вычисления для списков инициализации, нужно вынести их в отдельные функции что на C++, что на D. В таком случае инициализация будет вполне элементарной. Улыбающийся
Вызов одной формы конструктора из другой будет в новом стандарте.

3. Конструкторы относятся к ненаследуемому интерфейсу класса, т.к. при его вызове, ты всё равно явно указываешь имя класса, и параметры конструктора.
Единственное исключение: наследования шаблона от своего параметра. В этом случае мы можем просто не знать сигнатуру конструктора. Хотя есть некоторые приёмы, позволяющие обойти это ограничение.

4. Множественное наследование реализовано в С++ в наиболее общем виде из всех мне известных.
В D поддерживаются 2 ограниченных способа - наследование интерфейсов и миксины.
В любом случае - множественное наследование само по себе довольно сложная вещь, поэтому даже при применении этих ограниченных способов можно изрядно напутать.
Так что тут сложность самой концепции. Улыбающийся

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

Вывод: D просто небольшое "подсахаривание" С++ и его знаменитых наследников Java и C# принципиально нового ничего нет. Переносимости, по сравнению с С++ или Java нет. Библиотек почти нет. Документации нормальной нет. Стандарта или твёрдого плана развития нет. Вакансий нет.
Для нормально знающего С++ смысла переходить нет - слишком мало плюсов.
Для новичка следует выбирать именно из Java, C#, D ориентируясь на перспективы роста и развития.

Ну а если поиграться - язычёк прикольный. Улыбающийся
« Последнее редактирование: Ноябрь 28, 2008, 11:25 от Tonal » Записан
Kotofay
Гость
« Ответ #110 : Ноябрь 28, 2008, 18:58 »

Плюсы язык слишком высокого уровня. Надо напрягать моск ассемблером.
Очень даже наоборот, очень близок к ассемблеру.
динамически добавлять методы нельзя. Зачем эта рюшечка в виде invokeMethod()
Читай внимательно QAxObject, там всё написано. И добавление динамического слота и вызовы.
« Последнее редактирование: Ноябрь 28, 2008, 19:06 от Kotofay » Записан
Eugene Efremov
Гость
« Ответ #111 : Ноябрь 28, 2008, 19:06 »

1. Из функции pref нужно возвращать std::string или константную ссылку на неё

...и тащить с собой STL и весь ворох связанных с ней проблем.
Не хотел я затрагивать тему — уж больно необъятная... Если вкратце — то грабли, связанные с этой библиотекой, можно разбить на две категории:

1. Обобщенные проблемы обобщенного программирования в C++. Когда в этом языке появились шаблоны — никто не планировал, что их будут использовать для чего-то, более сложного, чем элементарные реализации контейнеров. В результате — никто и не позаботился, чтобы они были для этого приспособлены. Как результат — их использование для подобных целей получилось, мягко говоря, не слишком удобным. Я хорошо помню, как Страуструп в своей книге ругался на strcat и strcpy, гордо заявляя при этом, что теперь для этих целей можно использовать '+' и '='. И что мы получили в результате? Совершенно нечитабельные конструкции из адаптеров, алгоритмов, биндлеров, хрен знает чего еще, по сравнению с которыми все содержимое cstring — просто детский лепет...

2. Вторая категория проблем проистекает из того, что процедуре стандартизации была подвергнута совершенно сырая и неопробованная технология. Думаю, не ошибусь, если скажу, что во время разработки стандарта прототип STL находился, в лучшем случае, на стадии β-тестирования (а скорее всего — α). Думаю, все здесь знают, насколько часто приходится менять интерфейсы библиотек на начальной стадии их разработки. И все помнят, как сильно изменился, скажем, интерфейс Qt при переходе с 3 на 4. STL же оказалась лишена возможности подобного развития. Сырую библиотеку отлили в бетоне. И вот уже больше десятка лет мы вынуждены терпеть ее грабли, косяки, непродуманный интерфейс и отсутствие элементарных удобств.

К чему я все это говорю? А к тому, что, в результате всего этого, многие программисты (включая вашего покорного слугу) STL, прямо скажем, недолюбливают. И стараются, насколько это возможно, дела с ней не иметь. В результате, отказавшись от char*, один будет юзать QString, другой — wxString, третий таки воспользуется std::string, четвертый вообще напишет какой-нибудь my::string, а пятый будет думать, как со всей этой херней взлететь заставить их всех ужиться в одном проекте...

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


2. Если нужны какие-то дополнительные сложные вычисления для списков инициализации, нужно вынести их в отдельные функции что на C++, что на D. В таком случае инициализация будет вполне элементарной. Улыбающийся

Да, с этим примером погорячился. Но все равно — если конструкторов несколько, мы вынуждены писать список инициаторов для каждого из них. Имеем дублирование кода.

Вызов одной формы конструктора из другой будет в новом стандарте.

Это хорошо. Вот только когда это будет?..


3. Конструкторы относятся к ненаследуемому интерфейсу класса, т.к. при его вызове, ты всё равно явно указываешь имя класса, и параметры конструктора.

При вызове — да. Но я то не про вызов. Попробую разъяснить подробнее. Допустим, есть у нас навороченный библиотечный класс. В нем пара десятков конструкторов, ага. И вот хочешь ты расширить/подправить его интерфейс, добавив/перекрыв два-три метода. Как это делается? Правильно, путем создания производного класса... Мне продолжать?..

А избежать этого геморроя можно было бы очень просто, если бы элементарные конструкторы вида
Код
C++ (Qt)
foo::foo(int x, int y) : bar(x, y) {}
 
создавались компилятором автоматически. Ну или, если кто-то боится, что такая автоматизация создаст новые грабли, после добавления в класс foo строки вида
Код
C++ (Qt)
using bar::bar;
 

Ничего подобного ни в C++, ни, afiak, в D не наблюдается. Вроде бы, есть, опять-таки, некие слухи, что нечто подобное будет в грядущем стандарте, на который вся надежда...

4. Множественное наследование реализовано в С++ в наиболее общем виде из всех мне известных.
В D поддерживаются 2 ограниченных способа - наследование интерфейсов и миксины.
В любом случае - множественное наследование само по себе довольно сложная вещь, поэтому даже при применении этих ограниченных способов можно изрядно напутать.
Так что тут сложность самой концепции. Улыбающийся

С++ не единственный язык, поддерживающий этот самый «наиболее общий вид». Полноценное множественное наследование есть, например, в перле и питоне. И там никто от связанных с ним проблем не страдает. Потому что четко определён порядок приоритета, в котором должны разрешаться связанные с этом механизмом коллизии. В C++ достаточно было бы ввести самые простые правила, например такие:

1. В случае неоднозначности вызова ф-ций членов, приоритет имеют ф-ции, принадлежащие классу, идущему первым в списке базовых классов, если в производном классе явно не определено иное путем использования директивы using.

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

И уже механизм множественного наследования стал бы значительно более удобным.
И язык, который ставит своей целью улучшить C++, должен был бы сделать с множественным наследованием что-нибудь в этом роде, а не обрезать его, загоняя проблему вглубь...


Вывод: D просто небольшое "подсахаривание" С++ и его знаменитых наследников Java и C# принципиально нового ничего нет. Переносимости, по сравнению с С++ или Java нет.

Для нормально знающего С++ смысла переходить нет - слишком мало плюсов.
Для новичка следует выбирать именно из Java, C#, D ориентируясь на перспективы роста и развития.

Ну, если сравнивать с java — то единственное ее преимучщество оказывается в удобствах динамической линковки и независимости от платформы. В C++ для нормального создания плагинов требуется инструмент вроде Qt, да и в D это не самая тривиальная вещь. Здесь же все решается на счет раз.

Но по всем остальным параметрам java проигрывает обоим этим языкам. И очень сильно. В ней не оставили и половины возможностей C++... Так что, если D оценивать на четверку с минусом, то жабе больше двойки с плюсом не поставишь...  Улыбающийся

Что касается C# — еще недавно я бы про этот язык сказал то же самое (клон жабы... клон кастрированной копии C++). Но у него тот плюс, что он активно развивается. И сейчас там очень много новых возможностей, которых я просто не знаю. Так что ничего про него не буду говорить.

Еще из языков этого уровня уровня можно вспомнить perl6, который значительно лучше их всех. У него только один недостаток: его не существует. И он появится — одному Ларри Уоллу известно...


Библиотек почти нет. Документации нормальной нет. Стандарта или твёрдого плана развития нет. Вакансий нет.
Ну а если поиграться - язычёк прикольный. Улыбающийся

Вот именно, что только поиграться.  Грустный
Когда я только наткнулся на этот язык — попробовал написать на нем одну нужную мне утилиту. Получил — из-за кривой сборки мусора, не иначе — страшные тормоза и утечки. Долго ругался, потом переписал все на C++ и спокойно пользуюсь уже несколько лет...
Записан
Tonal
Гость
« Ответ #112 : Декабрь 02, 2008, 09:52 »

...и тащить с собой STL и весь ворох связанных с ней проблем.
STL уже есть в любом компиляторе, т.к. является частью стандарта.
Так что "тащить" ничего никуда не нужно - оно уже там. Улыбающийся

1. Обобщенные проблемы обобщенного программирования в C++. Когда в этом языке появились шаблоны — никто не планировал, что их будут использовать для чего-то...
Как бы проблемы с шаблонами не имеют отношения к использованию std::string. Улыбающийся
А совмещение (перегрузка) операторов действительно удобнее чем вызовы функций. По крайней мере в некоторых областях использования. Улыбающийся

2. Вторая категория проблем проистекает из того, что процедуре стандартизации была подвергнута совершенно сырая и неопробованная технология...
С моей точки зрения наоборот - слишком затянули, вот и появились многочисленные строки и контейнеры, которые теперь приходится согласовывать.
А был бы стандарт - все бы использовали его и пинали бы комитет чтобы добавил недостающие возможности.
Я начал использовать std::string как только она у меня появилась: borland c++ 5.0 - последний добилдеровский компилер от багланда.
И все строки от библиотек перевожу в std::string при первой возможности.
Написал некоторое количество обвязок конечно, но сейчас все выкинул, т.к. все они есть в boost-е. Улыбающийся

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

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

4. Множественное наследование реализовано в С++ в наиболее общем виде из всех мне известных.
С++ не единственный язык, поддерживающий этот самый «наиболее общий вид». Полноценное множественное наследование есть, например, в перле и питоне.
В python-е невозможно напрямую сделать аналог виртуального наследования. Поэтому таки не самый "общий" вид. Улыбающийся
Кроме того в python-е очень много делается "по умолчанию" и благодаря принципу "наименьшего удивления" это поведение почти всегда тебя устраивает.
Но вот когда нужно какое-то другое поведение, добиться этого сможет не каждый, т.к. не привыкли задумываться. Улыбающийся

Про оценки языкам - я их не ставлю. Улыбающийся
Мне кажется, что нужно просто хорошо представлять области применимости - что, где и зачем.
Тогда и воин не будет. Улыбающийся

П.С. Если на метопрограммирование на шаблонах смотреть не как самоцель или жупел, а как на средство уменьшения количества писанины, то опять же всё становиться на свои места.
Их нужно применять там и тогда, когда и где они нужны. Улыбающийся
Не случайно в Java и C# их таки добавили. И даже в последнюю Delphi! Улыбающийся
причём везде с выводом типов и возможностью метапрограммирования.
Хотя пока эти новые возможности довольно сильно уступают плюсовым, а с принятием нового стандарта отрыв ещё увеличится. Улыбающийся
Записан
Eugene Efremov
Гость
« Ответ #113 : Декабрь 07, 2008, 10:34 »

Как бы проблемы с шаблонами не имеют отношения к использованию std::string. Улыбающийся

Ну, если вспомнить, что std::string, строго говоря, тоже шаблон...   Подмигивающий
Но вообще да, здесь мы имеем дело только со второй частью проблемы, а именно — с сильно кривым дизайном этого класса... Улыбающийся

А совмещение (перегрузка) операторов действительно удобнее чем вызовы функций. По крайней мере в некоторых областях использования. Улыбающийся

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

2. Вторая категория проблем проистекает из того, что процедуре стандартизации была подвергнута совершенно сырая и неопробованная технология...
С моей точки зрения наоборот - слишком затянули, вот и появились многочисленные строки и контейнеры, которые теперь приходится согласовывать.

Кстати, да. Оба утверждения справедливы. Если бы контейнеры и строки были в языке с самого начала — как iostream — никто бы не стал пропихивать в стандарт сырую поделку товарища Степанова. И многих проблем удалось бы избежать.  Улыбающийся

Я начал использовать std::string как только она у меня появилась: borland c++ 5.0 - последний добилдеровский компилер от багланда.

Угу. Я примерно тогда же... После чего очень быстро переключился на сильно другие языки, в которых такого уродства не было. И лет пять старался держаться от C++ подальше. Потом вернулся... но мнения об STL не изменил. И более того, насмотревшись разных вкусных вещей, стал относиться к самому языку гораздо более скептически.

Проблема только в том, что альтернативы ему, в своей нише, увы, нет. D на такую альтернативу, как я, к сожалению, имел возможность убедиться, не тянет.  Грустный


П.С. Если на метопрограммирование на шаблонах смотреть не как самоцель или жупел, а как на средство уменьшения количества писанины, то опять же всё становиться на свои места.
Их нужно применять там и тогда, когда и где они нужны. Улыбающийся

Так разве ж я против метапрограммироавния? Я очень даже за. Улыбающийся
Просто речь о том, что в C++ этим инструментом крайне неудобно пользоваться. Напоминаю твою собственную цитату из соседней ветки, где речь как раз про это самое метопрограммирование и шла:
Блин, насколько всё же проще писать подобное на Haskell, где встроенное сопоставление с образцом...
И под этими я готов подписаться обеими руками: насколько проще было бы писать такие вещи на C++ если бы там было... [длинный список]. И, кстати, как раз в этой области у D очень много весьма полезных нововведений, которые превращают метопрограммирование на этом языке... ну, если не в конфетку, то, во всяком случае, в нечто, весьма далекое от той... ммм... субстанции, с которой мы имеем дело в C++  Улыбающийся



4. Множественное наследование реализовано в С++ в наиболее общем виде из всех мне известных.
С++ не единственный язык, поддерживающий этот самый «наиболее общий вид». Полноценное множественное наследование есть, например, в перле и питоне.
В python-е невозможно напрямую сделать аналог виртуального наследования. Поэтому таки не самый "общий" вид. Улыбающийся
Кроме того в python-е очень много делается "по умолчанию" и благодаря принципу "наименьшего удивления" это поведение почти всегда тебя устраивает.

И это правильно.
На счет же "не самого общего вида" в питоне — не буду спорить, я этого языка почти не знаю. (Хотя язык, судя по всему хороший... был бы хороший, если бы не уродский синтаксис с пробелами... эх...)

Но в перле, во всяком случае, добиться эффекта, эквивалентного виртуальному наследованию, очень легко. Вообще, хотя ООП там реализовано лишь частично (полностью — только в perl6, который неизвестно когда будет), в той части, в которой оно таки реализовано, остальным языкам следовало бы взять пример... Пожалуй, я даже приведу пример,  иллюстрирующий этот механизм. На C++, на перле, а потом, не уходить в офтопик, посмотрим, как с этим в D...

Итак, классическая схема "ромбиком", на классическом же примере — допустимые ходы шахматных фигур (слон, ладья и ферзь). Размер доски для простоты считаем бесконечным, коллизии не смотрим. Сперва — C++:
Код
C++ (Qt)
#include <iostream>
 
class Piece
{
private:
int x, y;
 
protected:
virtual bool chkmv(int dx, int dy) const = 0;
 
public:
Piece(int tx, int ty) : x(tx), y(ty)
{}
 
bool move(int dx, int dy)
{
if(!chkmv(dx, dy)) return false;
 
x+=dx;
y+=dy;
return true;
}
};
 
 
class Rook : virtual public Piece
{
protected:
virtual bool chkmv(int dx, int dy) const
{
return !(dx && dy);
}
 
public: // code dup :-(
Rook(int x, int y) : Piece(x, y)
{}
};
 
 
class Bishop : virtual public Piece
{
protected:
virtual bool chkmv(int dx, int dy) const
{
return dx==dy || (dx+dy)==0;
}
 
public: // code dup :-(
Bishop(int x, int y) : Piece(x, y)
{}
};
 
 
class Queen : public Rook, public Bishop
{
protected:
virtual bool chkmv(int dx, int dy) const
{
return Rook::chkmv(dx, dy) || Bishop::chkmv(dx, dy);
}
 
public: // code dup :-(
Queen(int x, int y)
: Piece(x, y), Rook(x, y), Bishop(x, y)  // MAPA3M :-E
{}
};
 
 
int main()
{
Queen q(0,4);
bool t1 = q.move(1,1);
bool t2 = q.move(0,4);
bool t3 = q.move(2,3);
std::cout<<t1<<" "<<t2<<" "<<t3<<"\n";
 
return 0;
}
 

Что мы тут имеем с гуся?
Ну, про дублирование кода в конструкторах я уже высказывался. Остается еще обратить внимание на совершенно феерическую фразу: «Piece(x, y), Rook(x, y), Bishop(x, y)». Два вызова из трех идут в никуда. И тем не менее без них оно не скомпилируется. В прошлом письме я уже про эту проблему с конструкторами виртуальных базовых классов говорил... Так что не будем о грустном. Посмотрим, лучше, как надо писать языки программирования это будет на perl:

Код
Perl
use strict;
package Piece;
 
sub new
{
my ($class, $tx, $ty) = @_;
my $this = { x => $tx, y => $ty };
 
# constructor inheritage
bless($this, $class);
return $this;
}
 
sub move
{
my ($this, $dx, $dy) = @_;
 
# virtual static function call
return 0 unless ref($this)->chkmv($dx, $dy);
 
$this->{x} += $dx;
$this->{y} += $dy;
return 1;
}
 
 
 
1;
package Rook;
our @ISA = qw(Piece);
 
sub chkmv
{
return !($_[1] && $_[2]);
}
 
 
 
1;
package Bishop;
our @ISA = qw(Piece);
 
sub chkmv
{
return $_[1] == $_[2] || ($_[1] + $_[2]) == 0;
}
 
 
 
1;
package Queen;
our @ISA = qw(Rook Bishop);
 
sub chkmv
{
return Rook::chkmv(@_) || Bishop::chkmv(@_);
}
 
 
1;
package main;
 
my $q = new Queen(0,4);
my $t1 = $q->move(1,1);
my $t2 = $q->move(0,4);
my $t3 = $q->move(2,3);
print "$t1 $t2 $t3\n";
 

Итак, что у нас тут. Во первых, конструктор Piece::new. Он, как ни трудно видеть, наследуется. Это во-первых. Во-вторых, поведение этого наследуемого конструктора полностью соответствует тому, что мы имеем при виртуальном наследовании в C++: поля базового объекта создаются в одном экземпляре. Наконец, в-третьих — мы полностью контролируем процесс создания класса, так что, если нам такое поведение не понравится, мы можем с легкостью переопределить конструктор и изменить его (единственное, при множественном наследовании тут нужно быть достаточно внимательным и не забывать, что, по умолчанию, если конструктора нет, будет вызван конструктор только для первого из списка базовых классов).

Еще стоит обратить внимание на конструкцию ref($this)->chkmv. Здесь ф-ция вызывается не по имени объекта, а по имени класса. Т.е. ее с полным правом можно считать "статичексой виртуальной" — тоже фича, ни в C++, ни в D не поддерживаемая. (Другой разговор, что в данном примере нам абсолютно безразлично, как вызывать ф-цию — по классу или по объекту, и простой вызов $this->chkmv прокатил бы точно также... Но это уже детали. Улыбающийся)


Теперь вернемся с небес на землю и посмотрим, можем ли мы перенести это дело в D. В том виде, который мы имеем сейчас — по-видимому нет. Во всяком случае, я с ходу не вижу способа эмулировать миксинами и интерфейсами виртуальное наследование. Так что эту задачу я оставляю тем, кто знает язык D лучше меня...

Но это значит, что сначала придется отрефакторить исходный пример. Избавившись от "ромбика". Из-за которого весь сыр-бор был:

Код
C++ (Qt)
#include <iostream>
 
class Piece
{
private:
int x, y;
 
protected:
virtual bool chkmv(int dx, int dy) const = 0;
 
public:
Piece(int tx, int ty) : x(tx), y(ty)
{}
 
bool move(int dx, int dy)
{
if(!chkmv(dx, dy)) return false;
 
x+=dx;
y+=dy;
return true;
}
};
 
 
 
template<class PieceStr> class PieceImpl : public Piece, protected PieceStr
{
protected:
// "using PieceStr::chkmv" don't work :-(
bool chkmv(int dx, int dy) const
{
return PieceStr::chkmv(dx, dy);
}
 
public: // code dup :-(
PieceImpl(int x, int y) : Piece(x, y)
{}
};
 
 
 
class RookStr
{
protected:
static bool chkmv(int dx, int dy)
{
return !(dx && dy);
}
};
 
class BishopStr
{
protected:
static bool chkmv(int dx, int dy)
{
return dx==dy || (dx+dy)==0;
}
};
 
class QueenStr : public RookStr, public BishopStr
{
protected:
static bool chkmv(int dx, int dy)
{
return RookStr::chkmv(dx, dy) || BishopStr::chkmv(dx, dy);
}
};
 
 
typedef PieceImpl  <RookStr> Rook;
typedef PieceImpl<BishopStr> Bishop;
typedef PieceImpl <QueenStr> Queen;
 
 
int main()
{
Queen q(0,4);
bool t1 = q.move(1,1);
bool t2 = q.move(0,4);
bool t3 = q.move(2,3);
std::cout<<t1<<" "<<t2<<" "<<t3<<"\n";
 
return 0;
}
 

Мы избавились от "ромбика", выделив операцию в отдельный класс стратегии, а основной класс сделав шаблонным. Тем не менее, если мы хотим сохранить нормальный полиморфизм, все наши объекты по-прежнему должны иметь общую базу. И здесь мы видим еще один недостаток C++: для разрешения неоднозначности нам недостаточно написать в производном классе using PieceStr::chkmv: компилятор не воспримет PieceStr::chkmv как конкретизацию абстрактной Piece::chkmv. Мы должны явно ее переопределить. Но зато теперь мы, например, можем определить PieceStr::chkmv как static. Или вообще ее переименовать. Очень полезно, да...  В замешательстве

Обратим еще внимание, что теперь наши классы стали шаблонами, и мы вынуждены использовать typedef, чтобы не засорять код громоздкими конструкциями...

Но, так или иначе, теперь мы можем свободно... почти... переписать этот код на D:

Код
D
import std.stdio;
 
class Piece
{
private:
int x, y;
 
protected:
abstract bool chkmv(int dx, int dy);
 
public:
this(int tx, int ty)
{
x=tx;
y=ty;
}
 
bool move(int dx, int dy)
{
if(!chkmv(dx, dy)) return false;
 
x+=dx;
y+=dy;
return true;
}
}
 
 
class PieceImpl(alias PieceStr) : Piece
{
protected:
mixin PieceStr; // ok
 
public: // code dup :-(
this(int x, int y)
{
super(x,y);
}
}
 
 
 
template RookStr()
{
bool chkmv(int dx, int dy)
{
return !(dx && dy);
}
}
 
template BishopStr()
{
bool chkmv(int dx, int dy)
{
return dx==dy || (dx+dy)==0;
}
}
 
template QueenStr()
{
bool chkmv(int dx, int dy)
{
mixin RookStr Rk;
mixin BishopStr Bs;
 
return Rk.chkmv(dx, dy) || Bs.chkmv(dx, dy);
}
}
 
 
alias PieceImpl!(RookStr)   Rook;
alias PieceImpl!(BishopStr) Bishop;
alias PieceImpl!(QueenStr)  Queen;
 
 
 
int main()
{
Queen q = new Queen(0,4);
bool t1 = q.move(1,1);
bool t2 = q.move(0,4);
bool t3 = q.move(2,3);
writefln("%d %d %d", t1, t2, t3);
 
return 0;
}
 

Классы стратей стали миксинами. Благодаря этому, мы избавились от необходимости переопределять chkmv в производном классе. Но тут же вылезла та проблема, о которой я говорил в первом письме. Множественное использование миксинов приводит к неоднозначности. Иными словами, код
Код
D
template QueenStr()
{
mixin RookStr Rk;
mixin BishopStr Bs;
 
bool chkmv(int dx, int dy)
{
return Rk.chkmv(dx, dy) || Bs.chkmv(dx, dy);
}
}
компилироваться не будет. Чтобы обойти эту проблему, мне пришлось включить "наследуемые" миксины внутрь функции...

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


И еще одна проблема в том, что исходный вариант — с "ромбиком" который — на D перевести так и не удалось. Здесь можно возразить, что вариант со стратегиями гораздо лучше. Да, обычно это так. "Ромбик" не масштабируется. Но, с другой стороны, и не плодит лишних сущностей, в отличии от. И я вполне могу представить себе ситуацию, где все это масштабирование и гибкость, присущее стратегиям, нафиг не нужно. А вот про принцип KISS везде полезно помнить Улыбающийся

И того, получаем, что в данном примере D снова на уровень C++ не тянет. Впрочем, сам C++ тоже решает исходную задачу не лучшим образом, требуя дублировать много кода.
Мораль: ждем, когда выйдет стабильная версия perl6 и сравниваем уже с ним. Подмигивающий
А пока ее нет, вывод один — «оба они хуже». Улыбающийся
Записан
Страниц: 1 ... 6 7 [8]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.22 секунд. Запросов: 21.