Показаны сообщения с ярлыком STL. Показать все сообщения
Показаны сообщения с ярлыком STL. Показать все сообщения

четверг, 17 ноября 2016 г.

Использование is_base_of и enable_if и детектирование типов во время компиляции

Допустим у нас есть некая функция, которая должна быть определена только для наследников определенного класса. Можно сделать это в рантайме, но детектирование типов в рантайме это дорого и к тому же некрасиво. C++11 предлагает более красивое решение.
#include <iostream>
#include <type_traits>
 
class A 
{
public:
    virtual void do_work() = 0;
};
 
class B : public A 
{
public:
    void do_work() override { std::cout << "B::do_work()" << std::endl; };
};
 
class C
{
public:
    void do_work() { std::cout << "C::do_work()" << std::endl; };
};
 
template<typename T>
typename std::enable_if<std::is_base_of<A, T>::value, void>::type
execute_work(T &t)
{
    t.do_work();
}

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

вторник, 27 марта 2012 г.

char to TCHAR

Очень популярная задача при использовании WinAPI и предлагается куча решений. Но большинство из них почему-то сходятся к выделению массива TCHAR в куче с помощью new либо использованию массива фиксированной длины. Мне больше всего нравится такое решение:

            //исходная строка
            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". Первое, что пришло в голову:
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

Любопытно скорость чтения последними двумя способами:

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 или еще какой-то простой тип. Хотя довольно часто все не так тривиально и приходится хранить и сортировать достаточно сложные классы. Приведу небольшой пример. Допустим, у нас есть двухвалютный банковский счет с деньгами, причем сумма, понятно, зависит от текущего курса каждой из валют (доллар и евро). Чтобы упорядочить множество таких счетов, нужно учесть зависимость от текущего курса. Продемонстрируем это в коде:
#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-параметрами.