UPD 03.12.2016: можно сделать это гораздо проще: //... template<typename T> bool execute_work(T &t) { static_assert(std::is_base_of<A, T>::value, "T must be subclass of A"); t.do_work(); } int main() { B b; C c; execute_work(b); //OK execute_work(c); //compile error } И теперь у нас компилятор будет выдавать именно то сообщение, которое мы указали в static_assert |
Показаны сообщения с ярлыком STL. Показать все сообщения
Показаны сообщения с ярлыком STL. Показать все сообщения
четверг, 17 ноября 2016 г.
Использование is_base_of и enable_if и детектирование типов во время компиляции
Допустим у нас есть некая функция, которая должна быть определена только для наследников определенного класса. Можно сделать это в рантайме, но детектирование типов в рантайме это дорого и к тому же некрасиво. C++11 предлагает более красивое решение.
Ярлыки:
C++,
C++11,
enable_if,
is_base_of,
static_assert,
STL,
template
вторник, 27 марта 2012 г.
char to TCHAR
Очень популярная задача при использовании WinAPI и предлагается куча решений. Но большинство из них почему-то сходятся к выделению массива TCHAR в куче с помощью new либо использованию массива фиксированной длины. Мне больше всего нравится такое решение:
Не надо выделять память руками и освобождать ее тоже не надо - за нас все делает STL. Уже на пару строчек меньше. И надежнее. Почему используем std::copy() вместо memcpy()? Потому что TCHAR, вообще говоря, это не тип, а макрос, который определяет тип (char или wchar_t) в зависимости от. То есть, в общем случае нельзя утверждать, что sizeof(char)==sizeof(wchar_t), а значит и memcpy() нам тоже не подходит и вместо него мы используем функцию std::copy(), которая копирует символы из одной строки в другую поштучно.
//исходная строка
const std::string dev_name = "COM4";
//создаем вектор нужной длины с учетом терминального нуля в конце std::vector<TCHAR> tchar_dev_name(dev_name.size() + 1);
//важно! используем std::copy(), а не memcpy()
std::copy(dev_name.begin(), dev_name.end(), tchar_dev_name.begin());
//нулевой символ в конце обязателен
tchar_dev_name[tchar_dev_name.size() - 1] = 0;
//дальше все как обычно
m_port = CreateFile(
&tchar_dev_name[0],
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
Не надо выделять память руками и освобождать ее тоже не надо - за нас все делает STL. Уже на пару строчек меньше. И надежнее. Почему используем std::copy() вместо memcpy()? Потому что TCHAR, вообще говоря, это не тип, а макрос, который определяет тип (char или wchar_t) в зависимости от. То есть, в общем случае нельзя утверждать, что sizeof(char)==sizeof(wchar_t), а значит и memcpy() нам тоже не подходит и вместо него мы используем функцию std::copy(), которая копирует символы из одной строки в другую поштучно.
понедельник, 6 июня 2011 г.
Работа с регулярными выражениями в std::tr1
TR1 - крайне полезное расширение STL, и, что самое важное, есть во многих компиляторах.
Допустим, нам нужно распарсить строку с диапазонами страниц для печати, разделенными запятыми. Строка типа "1, 2, 4-7, 10". Первое, что пришло в голову:
1
2
4
7 - 3
8 - 11
Все хорошо до тех пор, пока мы не попробуем вместо правильной строки ввести, например, такую неправильную строку: " 1 , 2, 4 , 3aaa - 7 , 9 - 11". В этом случае программа выведет следующее:
1
2
4
3
7
9 - 11
То есть программа распознала неправильный диапазон, как два отдельных числа. Нас такой результат не устраивает. Значит, надо указать, что диапазон должен содержать только цифры, пробелы и минус между цифрами. Порывшись в гугле, нашел такой вариант решения:
1
2
4
8 - 11
То есть неверный диапазон мы просто пропустили, вместо неверной его интерпретации, что и требовалось.
Для подсветки синтаксиса использовал highlight.hohli.com
Допустим, нам нужно распарсить строку с диапазонами страниц для печати, разделенными запятыми. Строка типа "1, 2, 4-7, 10". Первое, что пришло в голову:
using namespace std; string str = " 1 , 2, 4 , 3 - 7 , 9 - 11"; //отдельная цифра может быть окружена пробелами со всех сторон const string numb = "[\\s]*[0-9]+[\\s]*"; //диапазон -это либо одна цифра, либо две цифрыЮ разделенные тире const string diapason = numb + "(\\-" + numb + "){0,1}"; const std::tr1::regex regex_diap(diapason); //выводим все подстроки, соответствующие паттерну const tr1::sregex_token_iterator end; tr1::sregex_token_iterator iter = tr1::sregex_token_iterator(str.begin(), str.end(), regex_diap); for (; iter != end; ++iter) std::cout << *iter << std::endl;В результате программа выведет:
1
2
4
7 - 3
8 - 11
Все хорошо до тех пор, пока мы не попробуем вместо правильной строки ввести, например, такую неправильную строку: " 1 , 2, 4 , 3aaa - 7 , 9 - 11". В этом случае программа выведет следующее:
1
2
4
3
7
9 - 11
То есть программа распознала неправильный диапазон, как два отдельных числа. Нас такой результат не устраивает. Значит, надо указать, что диапазон должен содержать только цифры, пробелы и минус между цифрами. Порывшись в гугле, нашел такой вариант решения:
using namespace std; //входная строка string str = " 1 , 2, 4 , 5 - 7, 9 - 11"; //целое число, которое может быть обрамлено пробельными символами const string numb = "[\\s]*[0-9]+[\\s]*"; //вот тут мы используем т.н. просмотр вперёд и назад const string one_page = "(?<=(,|^))" + numb + "(?=(,|$))"; const string many_page = "(?<=(,|^))" + numb + "\\-" + numb + "(?=(,|$))"; //число или диапазон const string one_many_page = "(" + one_page + "|" + many_page + ")"; const tr1::regex rx(one_many_page); const tr1::sregex_token_iterator end; tr1::sregex_token_iterator it; for (it = tr1::sregex_token_iterator(str.begin(), str.end(), rx); it != end; ++it) std::cout << *it << std::endl;Самое интересное тут - это т.н. просмотр вперед и назад без захвата выражения. Подробнее - в википедии. Благодаря чему мы указываем, что любому отдельному числу должна предшествовать либо запятая, либо начало строки, отделенные нулем или большим числом пробелов и за ним должна следовать либо запятая, либо конец строки, отделенные нулем или большим числом пробелов:
const string numb = "[\\s]*[0-9]+[\\s]*"; const string one_page = "(?<=(,|^))" + numb + "(?=(,|$))";Аналогично с диапазоном чисел:
const string numb = "[\\s]*[0-9]+[\\s]*"; сonst string many_page = "(?<=(,|^))" + numb + "\\-" + numb + "(?=(,|$))";Ну и регулярное выражение, которое выбирает ИЛИ число ИЛИ диапазон:
const string one_many_page = "(" + one_page + "|" + many_page + ")";Теперь, если указать неверный диапазон чисел типа " 1 , 2, 4 , 7aaa - 3, 8 - 11", то на выходе получим:
1
2
4
8 - 11
То есть неверный диапазон мы просто пропустили, вместо неверной его интерпретации, что и требовалось.
Для подсветки синтаксиса использовал highlight.hohli.com
понедельник, 5 июля 2010 г.
Чтение с помощью STL
Очень интересный пример чтения файла с помощью STL:
http://easy-coding.blogspot.com/2009/02/stl.html
Любопытно скорость чтения последними двумя способами:
Если в VS2005 запускать без оптимизации, то каждый из этих двух тестов выполняется за 125 мс, а с оптимизацией - за 31 мс. То есть имеем четырехкратный прирост производительности.
http://easy-coding.blogspot.com/2009/02/stl.html
Любопытно скорость чтения последними двумя способами:
TEST(ReaderTest, istreambuf_iterator_tostring_short) {
std::string v(
(std::istreambuf_iterator<char>(
std::ifstream(Env::testfile())
)),
std::istreambuf_iterator<char>()
);
EXPECT_EQ(Env::testfile_sz(), v.length());
}
TEST(ReaderTest, istreambuf_iterator_tostring_short_auto_ptr) {
std::string v(
(std::istreambuf_iterator<char>(
*(std::auto_ptr<std::ifstream>(
new std::ifstream(Env::testfile())
)).get()
)),
std::istreambuf_iterator<char>()
);
EXPECT_EQ(Env::testfile_sz(), v.length());
}
Colored with dumpz.org
Если в VS2005 запускать без оптимизации, то каждый из этих двух тестов выполняется за 125 мс, а с оптимизацией - за 31 мс. То есть имеем четырехкратный прирост производительности.
четверг, 17 июня 2010 г.
STL: for_each и sort
В сети много примеров работы с STL, но часто в качестве шаблонного типа используется int или еще какой-то простой тип. Хотя довольно часто все не так тривиально и приходится хранить и сортировать достаточно сложные классы. Приведу небольшой пример. Допустим, у нас есть двухвалютный банковский счет с деньгами, причем сумма, понятно, зависит от текущего курса каждой из валют (доллар и евро). Чтобы упорядочить множество таких счетов, нужно учесть зависимость от текущего курса. Продемонстрируем это в коде:
На выходе консоли должно быть это:
В процессе выяснилась интересная вещь: на VS-2005 все замечательно работало, а на QT4 вдруг оказалось, что не хватает функции сравнения, где оба сравниваемых аргумента объявлены как const. После добавления перегруженной версии оператора (operator() (const Wallet, const Wallet &)) с константными объектами компилятор сообщил, что функции getUsd() и getEur() не могут вызываться в константном объекте, так как они сами не константные. После того, как они были объявлены константными, все заработало. Из чего можно сделать вывод, что реализации STL в разных компиляторах могут отличаться.
Также оказалось, что функция qSort() из библиоеки Qt работает так же, как и stl::sort() с объектами std::vector и не требует функции с const-параметрами.
#include <QtCore/QCoreApplication>
#include <QDebug>
#include <iostream>
#include <algorithm>
#include <list>
#include <math.h>
using namespace std;
void printInt(int numb);
//класс нашего банковского "кошелька"
class Wallet {
private:
string owner;
double eur;
double usd;
public:
//в конструкторе указываем дополнительные параметры
//в данном случае это курсы валют относительно гривны
Wallet(string owner, double usd, double eur) {
this->owner = owner;
this->eur = eur;
this->usd = usd;
};
//как показывает практика, модификатор const тут необходим
//(см. пояснения к коду)
double getUsd() const { return usd; };
double getEur() const { return eur; };
void showInfo() {
cout << "Wallet of " << owner << " contains a " << usd << " USD and " << eur << " EUR " << endl;
};
};
void printWallet(Wallet & wallet) {
wallet.showInfo();
};
//функтор для вывода информации
class WalletUahLess {
private:
double uahToUsd;
double uahToEur;
public:
WalletUahLess(double uahToUsd, double uahToEur) {
this->uahToUsd = uahToUsd;
this->uahToEur = uahToEur;
};
//это обычный вариант сравнения двух "уошелкьков"
bool operator() (Wallet & wallet1, Wallet & wallet2) {
double uah1 = wallet1.getUsd()*uahToUsd + wallet1.getEur()*uahToEur;
double uah2 = wallet2.getUsd()*uahToUsd + wallet2.getEur()*uahToEur;
return uah1 < uah2;
};
//также бывает необходим и вариант с модификаторами const
bool operator() (const Wallet & wallet1, const Wallet & wallet2) {
double uah1 = wallet1.getUsd()*uahToUsd + wallet1.getEur()*uahToEur;
double uah2 = wallet2.getUsd()*uahToUsd + wallet2.getEur()*uahToEur;
return uah1 < uah2;
};
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
vector<Wallet> wallets;
//заполняем контейнер
wallets.push_back(Wallet("Vasya", 960.50, 780.3));
wallets.push_back(Wallet("Vova", 954.2, 789.56));
wallets.push_back(Wallet("Piotr", 961.80, 792.67));
wallets.push_back(Wallet("Ivan", 970.34, 760.50));
wallets.push_back(Wallet("Sidor", 1090.76, 700.90));
cout << endl << "before sort(): " << endl;
for_each(wallets.begin(), wallets.end(), printWallet);
//сортируем при одном курсе
std::sort(wallets.begin(), wallets.end(), WalletUahLess(8.5, 9.6));
cout << endl << "after sort() with WalletUahLess(8.5, 9.6): " << endl;
for_each(wallets.begin(), wallets.end(), printWallet);
//сортируем при другом курсе
sort(wallets.begin(), wallets.end(), WalletUahLess(7.8, 11.5));
cout << endl << "after sort() with WalletUahLess(7.8, 11.5): " << endl;
for_each(wallets.begin(), wallets.end(), printWallet);
return a.exec();
}
void printInt(int numb) {
cout << numb << endl;
}
* This source code was highlighted with Source Code Highlighter.На выходе консоли должно быть это:
before sort():
Wallet of Vasya contains a 960.5 USD and 780.3 EUR
Wallet of Vova contains a 954.2 USD and 789.56 EUR
Wallet of Piotr contains a 961.8 USD and 792.67 EUR
Wallet of Ivan contains a 970.34 USD and 760.5 EUR
Wallet of Sidor contains a 1090.76 USD and 700.9 EUR
after sort() with WalletUahLess(8.5, 9.6):
Wallet of Ivan contains a 970.34 USD and 760.5 EUR
Wallet of Vasya contains a 960.5 USD and 780.3 EUR
Wallet of Vova contains a 954.2 USD and 789.56 EUR
Wallet of Piotr contains a 961.8 USD and 792.67 EUR
Wallet of Sidor contains a 1090.76 USD and 700.9 EUR
after sort() with WalletUahLess(7.8, 11.5):
Wallet of Ivan contains a 970.34 USD and 760.5 EUR
Wallet of Vasya contains a 960.5 USD and 780.3 EUR
Wallet of Vova contains a 954.2 USD and 789.56 EUR
Wallet of Sidor contains a 1090.76 USD and 700.9 EUR
Wallet of Piotr contains a 961.8 USD and 792.67 EUR
Комментировать особо нечего, но стоит добавить, что использовать класс типа WalletUahLess вместо функции - это намного более гибкий и удобный способ сортировки, чем с помощью функции, т.к. позволяет задать дополнительные параметры - в данном случае различные курсы валют, не создавая при этом новый класс или функтор для сравнения.В процессе выяснилась интересная вещь: на VS-2005 все замечательно работало, а на QT4 вдруг оказалось, что не хватает функции сравнения, где оба сравниваемых аргумента объявлены как const. После добавления перегруженной версии оператора (operator() (const Wallet, const Wallet &)) с константными объектами компилятор сообщил, что функции getUsd() и getEur() не могут вызываться в константном объекте, так как они сами не константные. После того, как они были объявлены константными, все заработало. Из чего можно сделать вывод, что реализации STL в разных компиляторах могут отличаться.
Также оказалось, что функция qSort() из библиоеки Qt работает так же, как и stl::sort() с объектами std::vector и не требует функции с const-параметрами.
Подписаться на:
Сообщения (Atom)