Шаблоны в C++
О том, что шаблоны вещь нужная и полезная распространяться много не буду, это и так ясно, с одной стороны... а с другой, в каких реальных задачах их необходимо применять, т.е. без них не обойтись? Мне пока хватало классического ООП без шаблонов. Под "применять" я имею ввиду самому разрабатывать, а не использовать готовые STL/boost.
А применять удобно, очевидно же, когда надо обобщить ( частично возможно) какой то код, не привязываясь к конкретным, возможно обладающим совсем разными свойствами, параметрам.
Конкретные примеры какой смысл приводить? Помоему в сети достаточно информации по этому - просто кишит конкретными примерами. Да и boost с stl можно рассмотреть в качестве конкретных примеров, где люди для себя использовали шаблоны.
Если же это опрос - использует ли кто то шаблоны в С++ (в смысле разрабатывает), то да - я использую. По мере возникновения необходимости. Тоесть когда как. Но регулярно приходится.
...
Под "применять" я имею ввиду самому разрабатывать, а не использовать готовые STL/boost.
Возможно, я продемонстрирую свою дремучесть, но подозреваю, что в C++ шаблоны удобно использовать в основном именно в таких библиотеках. То есть в коде, который не сильно меняется при разработке программы.
Может я что-то не понимаю, но у меня сложилось впечатление, что чтобы иметь возможность повторно использовать код на шаблонах, его надо весь засунуть в хедер (вроде бы оно и логично для подобного языка). Но при этом получится дурная ситуация - при любом малейшем изменении в этом хедере будет перекомпилироваться весь зависимый код. С абстрактными классами тут удобнее.
Я не разрабатываю подобных библиотек, потому за все использование C++ на практике помню только пару случаев, когда использовал шаблоны. Но использовал только для функций, не для классов.
Я задал вопрос к тому, что не вижу их применимости в реальных задачах не в качестве библиотек. То есть, в реальной задаче стараемся похожее объединить в класс (иерархию классов), и для каждой сущности эти иерархии классов свои, и смысла нет "ошаблонивать" их поведение, т.к. оно у них разное. Вот мне и интересны примеры задач (не библиотек многофункциональных), где применение шаблонов оправдано и дает реальный выигрыш. ask послал на... в гугль, но я чет затрудняюсь с запросом, чтоб он мне вывел нужный результат.
Теоретически, могут использоваться в некоторых оптимизациях. Можно вспомнить про тот же факториал на шаблонах. Тут люди развлекаются, например:
http://www.rsdn.ru/forum/cpp/1449390.flat.aspx
Но я всё это понимаю как интересные задачки. На практике редко соображаю, что можно применить шаблон.
Пример какой нидь можно поглядеть, о чем вообще речь.
{
void print()
{
std::cout << "Class A" << std::endl;
}
};
struct B
{
void print()
{
std::cout << "Class B" << std::endl;
}
};
template<class C>
void func()
{
C().print();
}
int main()
{
void (*f[])() = {func<A>, func<B>, func<B>, func<A>};
for(size_t i = 0; i < sizeof(f)/sizeof(f[0]); ++i)
f();
std::cout << "=====" << std::endl;
std::vector< void(*)() > vf;
vf.push_back(func<A>);
vf.push_back(func<B>);
vf.push_back(func<A>);
vf.push_back(func<B>);
for(size_t i = 0; i < vf.size(); ++i)
vf();
return 0;
}
Возможно, тут есть ошибки, но гсс собрал и выполнил без предупреждений.
В общем, в этом примере я почти подобрался к утиной типизации :)
Конечно, в общем случае проще всё сделать просто используя указатели на функции... Но может пригодится, если задействовать несколько функций из класса.
Допустим, масса и температура - числовые величины, но складывать их нельзя, т. к. это разные физические сущности. Хочу чтобы компилятор следил за этим.
Что-то в этом роде: http://www.rsdn.ru/forum/src/1824757.flat.aspx
Почитал. Пока понял только то, что приведённый units.hpp должен создавать робот на основе неких таблиц (ну очень уж много дублирования и магических чисел для человека). Но, с другой стороны, если робот всё равно нужен, то зачем шаблоны?
Что-то в этом роде: http://www.rsdn.ru/forum/src/1824757.flat.aspx
Оффтоп.
Как только цппшники не выёживаются, лишь бы Ocaml или Nemerle не использовать. :D
Как только цппшники не выёживаются, лишь бы Ocaml или Nemerle не использовать. :D
Проблема в том, что любители экзотики не могут кратко объяснить, за что они эту экзотику любят. Их любимые примеры показывают, что эти языки просто включают в себя встроенный синтаксический анализатор. Более того, весь их синтаксис вокруг этого строится.
Вот потому на них и удобно парсеры и другие подобные штуки строить. Но есть ли у них другие преимущества?
Но, в общем, радует упоминание OCaml-а;;
Кто ясно мыслит - тот ясно излагает.
Кончай фантазировать, давно бы уже попробовал эту "эзотерику".
Не путай, я сказал - экзотику :)
А ты ясно мыслишь? Можешь изложить вот на примере этих гнусных классов с переводом одних размерностей в другие как надо решить эффективнее и проще?
Сам я подозреваю, что с помощью того же C++ можно всё это организовать без шаблонов, не потеряв ничего в скорости, но сократив код раз в пять. Но пока не уверен.
{
void func1()
{
std::cout << "A.func1" << std::endl;
}
void func2()
{
std::cout << "A.func2" << std::endl;
}
};
struct B
{
void func1()
{
std::cout << "B.func1" << std::endl;
}
void func2()
{
std::cout << "B.func2" << std::endl;
}
};
struct DuckParent
{
virtual void func1() = 0;
virtual void func2() = 0;
virtual ~DuckParent(){}
};
template<class C>
struct Duck: public DuckParent
{
C c;
void func1(){c.func1();}
void func2(){c.func2();}
};
int main()
{
std::vector<DuckParent*> dv;
dv.push_back(new Duck<A>);
dv.push_back(new Duck<B>);
for(size_t i = 0; i < dv.size(); ++i)
{
dv->func1();
dv->func2();
}
for(size_t i = 0; i < dv.size(); ++i)
delete dv;
return 0;
}
Короче, с помощью шаблонов и композиции вывернул наизнанку абстрактный класс.
А ты ясно мыслишь?
Неее, мне эти размерности сейчас не особенно-то нужны.
Я не умею шаблоны готовить, они слишком примитивны (ими нельзя файл прочитать или в базу залезть) :)
Вот тут чувак, мыслит ясно.
В МФЦ есть некий класс для работы с БД - CRecordset - тупо апи ф-ции обернули в класс, функциональности никакой. Унаследовался от него, и перегрузил/добавил несколько ф-й и операторов:
{
private:
...........
map<string, VARIANT> m_fieldValues;
...........
public:
bool Open
{
// некоторые движения
CRecordset::Open(/*мои параметры*/);
}
VARIANT& operator[](string fieldName)
{
return m_fieldValues[fieldName];
}
...............
};
далее от этого класса надо было унаследоваться и создать уже 4 класса, отображающих сущности: Товары, Поставщики, ФизЛица, Заказы. Т.е. все сущности обладают схожими операторами и ф-ями доступа к данным, но в то же время с самими данными работают каждая по своему.
Вопрос: как ошаблонить можно подобную ситуацию?
Вопрос: как ошаблонить можно подобную ситуацию?
Тут надо уточнить некоторые моменты. Почему нельзя просто сделать наследников и перегрузить в них методы?
Но если у них нет ничего общего, кроме заголовков некоторых методов, но можно применить то, что я привел в предыдущем сообщении. Каждый класс (Товары, Поставщики, ФизЛица, Заказы) делается отдельно и может использоваться отдельно как конкретный класс (в частях этой программы или даже в другой программе). А если необходимо, например, соединить их в едином массиве, то можно использовать класс-утку (в данном случае, что-то вроде враппера), который и отвечает за полиморфизм. Короче, тут не надо заранее определять общий абстрактный класс, а создавать его аналог в том месте, где он потребуется.
оно так и было сделано:
{
........
};
class CPerson : public CExtRecordset
{
......
};
// и т.д.
а вот в шаблонах как это будет? При чем речь не о том, что будет ли это в шаблонах проще и легче, а вообще, в принципе, возможно ошаблонить подобное? я чет допетрить никак не могу. Все мои мысли о шаблонировании почему то переиначивают иерархию объектов с ног на голову.
С этим согласен. Но у меня есть большое подозрение, что простого си и умения создавать хитрые make-файлы хватит на это.
Типа, запускаем make-файл, он компилит мелкую прожку, которая на основе файла с данными генерит нужный исходный код. А затем тот же make-файл собирает основную прогу с этим кодом.
Думаю, вполне сравнимая альтернатива вашим макросам. Или нет?
Безобразие! Как на языке, который претендует на возможность дать ответ о смысле жизни, можно писать такое:
{
$(typeName : name)(unit.Value * value)
}
static public @/(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value / value)
}
static public @+(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value + value)
}
static public @-(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value - value)
}
И там сплошь такие чудеса.
Нет уж, это не тот, кто мыслит ясно. Ну, или язык уныл. Но, подозреваю, что дело не в языке.
Или я опять попутал и этот файл создавал робот?
Типа, запускаем make-файл, он компилит мелкую прожку, которая на основе файла с данными генерит нужный исходный код. А затем тот же make-файл собирает основную прогу с этим кодом.
Думаю, вполне сравнимая альтернатива вашим макросам. Или нет?
Нет.
Я сейчас вечерами допиливаю утиллиту тестирования парсера C#.
Сейчас тест выглядит следующим образом, это обычный cs-файл:
namespace X
{
class Foo
{
struct Bar {}
}
}
Макрос поднимает этот файл с диска, выцепляет выражение в теге PATTERN (это выражение сопоставления с образцом Nemerle, синтаксис будет упрощаться), разбирает его штатным парсером Nemerle, конечно это все можно и без макросов делать, но вот дальше начинаются [COLOR="Silver"]ковровые бомбометания[/COLOR] чудеса.
Парсер C# при разборе текста строит так называемое AST (abstract syntax tree) - дерево разбора. Это такая некислая развесистая структура в памяти (на самом деле это DAG) в которой задействовано 40-50 классов.
Чтобы организовать юнит-тестирование парсера необходимо как-то эту структуру анализировать - реализовывать Посетитель, который проверит, есть ли в этом дереве заданный узел (директива PATTERN какраз описывает этот узел). Но вот ведь незадача - в этом AST десятки классов. Писать посетителя такой структуры вручную для каждого теста офигительно накладно!
Ответ конечно прост - я написал макрос, который генерирует мне нужного посетителя (50 строк, код построения обобщенного посетителя уже был - 250 строк). При том этот макрос совершенно не знает ни о каких парсерах C# и AST - он строит код обхода экземпляра произвольного типа. Тип этот фиксируется на этапе компиляции, и на выходе получается строго типизированный шустрый код безо всяких рефлексий.
Безобразие! Как на языке, который претендует на возможность дать ответ о смысле жизни, можно писать такое:
Это квазицитирование. С его помощью изготавливаются "куски кода", кторые потом подставляются куда-либо. Конкретно эти - в тела классов.
Я не понял или ты не понял.
Твое описание я понял так: пишу макрос, который сгенерит мне определённый код (текст), который скомпилирую и на выходе получу то, что хочу.
Но ты не объяснил, почему тоже самое не может делать си. Никто же не обязывает код на си компилироваться в один этап. Можно написать код на си, который скомпилируется и выдаст следующий код, а тот скомилировавшись выдаст ещё один и т.д. до бесконечности. И всё это будет управляться единым make-файлом.
При этом даже не обязательно, чтобы всё делалось на одном и том же языке. Можно использовать для каждого этапа использовать тот, который удобнее для конкретной задачи.
Но это же не средствами языка делалось? Это дублирование, пусть даже оно и сгенерировано было, например, редактором, но читать и редактировать такое - весёлого мало. Некрасиво, в общем.
Внешнее кодогенерирование (текстогенерирование) это не альтернатива макросам.
Сразу же возникают две проблемы: интеллисенс, вменяемые сообщения об ошибках.
Код в Nemerle это совсем не текст :)
Покажи мне как в Си выполнить обход членов структур и я заброшу Nemerle.
Квазицитирование это средство языка.
З.Ы. о выводе типов я уже и не упоминаю
...
И там сплошь такие чудеса.
А что не так? Например, при чтении этого кода я внимательно разобрал первый метод, а по остальным лишь проскользил взглядом - их смысл уже вполне понятен. А если оформить это всё в цикл+массив, то на разбор смысла этого цикла, генерирующего методы, у меня уйдёт гораздо больше времени, чем на разбор просто одного метода. Не вижу выгоды.
Kogrom, имхо, ты зачастую чересчур пытаешься избежать дублирования кода. Я не спорю: если бы таких почти одинаковых кусков кода было не 4, а 40, то имело бы смысл сделать цикл или что-то ещё. А всего несколько штук проще и быстрее написать вручную. Ведь цикл ещё придумать надо, и отладить, как бы прост он ни был. Изменить 4 куска тоже не составит труда. Имхо.
P.S. Kogrom, hardcase - продолжайте, читать вас очень интересно и полезно.
Сложновато получается: для упрощения одной сложной задачи использовать сразу несколько языков. Я вот хотел кое-что в C# упростить, написал пару макросов на VBA. Потом понял, что действовать они будут лишь в Visual Studio, и желание юзать VBA пропало. Хочу, чтобы кодогенерация, макросы и т. д. и т. п. были на том же самом языке. И чтобы это всё вместе взятое работало в любой IDE, которая поддерживает этот язык, на любой платформе. Похоже, Nemerle это обеспечивает.
Сразу же возникают две проблемы: интеллисенс, вменяемые сообщения об ошибках.
Вроде бы звучит логично.
Ненене, сишников и так хватает. Тем более, что и задачу можно трактовать по разному. А главное - лень осиливать эту задачу.
Так там же Ctrl-C, Ctrl-V, а не средство языка.
Kogrom, имхо, ты зачастую чересчур пытаешься избежать дублирования кода. Я не спорю: если бы таких почти одинаковых кусков кода было не 4, а 40, то имело бы смысл сделать цикл или что-то ещё. А всего несколько штук проще и быстрее написать вручную. Ведь цикл ещё придумать надо, и отладить, как бы прост он ни был. Изменить 4 куска тоже не составит труда. Имхо.
Тут проблема больше в том, что при изменении кода можно в каком-то куске что-то забыть.
А что тут сложного? Могу показать на квази-коде:
{
static public @${symbol}(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value ${symbol} value)
}
}
Естественно, я придираюсь, потому что код не мой. Себе я многие безобразия прощаю :)
Так там же Ctrl-C, Ctrl-V, а не средство языка.
Ну так макросы для того и нужны чтобы ликвидировать дублирование кода. Исходного кода - кода, который пишет человек.
Так потому меня и удивил этот код. Потому я и спросил, не написал ли его робот. А в ответ получил разные хитрые термины. Но вроде бы все недопонимания разрешились.
В МФЦ есть некий класс для работы с БД - CRecordset - тупо апи ф-ции обернули в класс, функциональности никакой. Унаследовался от него, и перегрузил/добавил несколько ф-й и операторов:
{
private:
...........
map<string, VARIANT> m_fieldValues;
...........
public:
bool Open
{
// некоторые движения
CRecordset::Open(/*мои параметры*/);
}
VARIANT& operator[](string fieldName)
{
return m_fieldValues[fieldName];
}
...............
};
далее от этого класса надо было унаследоваться и создать уже 4 класса, отображающих сущности: Товары, Поставщики, ФизЛица, Заказы. Т.е. все сущности обладают схожими операторами и ф-ями доступа к данным, но в то же время с самими данными работают каждая по своему.
Вопрос: как ошаблонить можно подобную ситуацию?
Как вариант - использовать свойства ("Traits"):
/*вместо enum можно просто class PERSON; class PRODUCT; template <typename T> class RecordsetTraits;*/
template <RECORDSET>
class RecordsetTraits;
template<>
class RecordsetTraits <PERSON>
{
public:
static bool Open()
{
// некоторые движения
CRecordset::Open(/*мои параметры*/);
}
};
temlate<>
class RecordsetTraits <PRODUCT>
{
public:
static bool Open()
{
// некоторые движения
CRecordset::Open(/*мои параметры*/);
}
};
template <RECORDSET R, typename RT = RecordsetTraits<R> >
class RecordSet
{
map<string, VARIANT> m_fieldValues;
public:
bool Open()
{
return RT::Open();
}
VARIANT& operator[](string fieldName)
{
return m_fieldValues[fieldName];
}
};
Больше ниче не буду по новой писать - извиняйте хлопцы. Терпения не хватит. )
Больше ниче не буду по новой писать - извиняйте хлопцы. Терпения не хватит. )
Как серпом по мошонке прям... ты у ж соберись как нидь, восстанови хоть половину. Ибо вопрос животрепещущий, как оказалось я в шаблонах то как свинья в апельсинах, что Нездешний понаписал для меня темный лес.
По моему тут вот шикарный пример использования шаблонов не для библиотек (это часть кода игрушки Open Transport Tycoon Deluxe, реализующая алгоритм поиска пути для различных видов транспорта). В принципе, в исходниках еще много мест, где используются шаблоны (к примеру, с их использованием реализован механизм хранения объектов игрового мира).
Кстати, в аналогичной ситуации гуглохром мне текст обратно показал с восстановленными вкладками. ;)
Честно говоря, я и сам шаблоны использую нечасто. В основном из-за того, что при статическом полиморфизме (на шаблонах) пропадает возможность создавать и обрабатывать разнородные коллекции (типа вектора из указателей на базовый класс). Да и код воспринимается сложнее.
А когда возникает необходимость, подглядываю в книжку - Вандервуд, Джосаттис - "Шаблоны С++. Справочник разработчика" ;)
{
static public @${symbol}(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value ${symbol} value)
}
}
Естественно, я придираюсь, потому что код не мой. Себе я многие безобразия прощаю :)
Там есть определенные сложности, но они скорее связаны с некоторым неудобством API. Уменьшить дублирование можно, но в читабельности мы потеряем (просто написал в браузере):
"op_Subtract",
"op_Multiply",
"op_Divide" ];
def methods = ops.Map(op =>
<[ decl:
static public $(op : usesite)(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)( double.$(op : usesite)(unit.Value, value) )
}
]>);
Так наследуй шаблонный класс от абстрактного и будет возможность:
http://forum.codenet.ru/showpost.php?p=337061&postcount=15
при том, что полиморфизм реализуется за счёт шаблонов. Если я понял, это аналог твоих "Traits".
Да. Но тут проблема в том, что в C++ нет заточенности на такие финты. Поэтому они немного страшновато смотрятся. Как, в общем, и многие элементы из STL и boost...
Traits - эт не мои :) Насколько я понимаю, это уже довольно устоявшийся термин (см. те же "Шаблоны С++" или WTL, например)
Нельзя сделать то же. В случае с шаблонами все классы независимы и сходны только заголовками функций. В случае без шаблонов - надо всем наследоваться от единого абстрактного класса.
Более того, при использовании шаблонов я могу группировать любые классы, выделять из них требуемые в конкретной точке методы, а без них - только наследников некоего абстрактного класса, получая нужные и ненужные методы.
Дельное определение нужности шаблонов.