The next revision of the C++ Standard, tentatively titled C++0x (the 'x' is a placeholder, intended to suggest that the revision will be finished before 2010, although some people like to remind us that "0x" introduces a hexadecimal constant, so C++0x really has to be finished by 2015), ...
30 апреля 2006
x in "C++0x"
В Dr.Djobb's Journal шутят по поводу даты выхода следующего стандарта C++:
28 апреля 2006
#define is evil
Как часто при написании Windows приложений приходится употреблять следующую строку:
А теперь попробуем следующий код:
Попробуем откомпилировать:
Вот вам и windows.h со своими #define...
Подобные вещи мешают использовать даже стандартную библиотеку - макросы min и max не дают воспользоваться шаблонами std::min и std::max.
Лучшее лекарство от подобного безобразия:
#include <windows.h>
А теперь попробуем следующий код:
// Loader.h
class Loader
{
public:
void LoadString();
}
// Loader.cpp
#include "Loader.h"
#include
void Loader::LoadString()
{
// code here
}
Попробуем откомпилировать:
error C2039: 'LoadStringA' : is not a member of 'Loader'
Вот вам и windows.h со своими #define...
Подобные вещи мешают использовать даже стандартную библиотеку - макросы min и max не дают воспользоваться шаблонами std::min и std::max.
Лучшее лекарство от подобного безобразия:
// SafeWindows.h
#pragma once
#include <windows.h>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#ifdef MessageBox
#undef MessageBox
#endif
...
// Any.cpp
#include "SafeWindows.h"
int main()
{
return ::MessageBoxW(NULL, L"#define is evil", L"Note", MB_OK);
}
20 апреля 2006
Передача динамически созданных объектов в DLL
Замечательное свойство shared_ptr заключается в том, что он хранит в себе ссылку на deleter - функцию, которую shared_ptr вызывает для удаления объекта.
То есть если мы создадим shared_ptr в EXE и передадим его в DLL, которая потом будет удалять объект из динамической памяти, то она вызовет operator delete (или другой заданный deleter), который находится в EXE! Это спасет нас от головной боли с несколькими копиями CRT (одна в EXE, другая в DLL), которая была бы если бы мы передали простой указатель, а не shared_ptr.
Правда возникает вопрос: не возникнет ли у shared_ptr проблем с подсчетом ссылок при пересечении границы EXE-DLL?
То есть если мы создадим shared_ptr в EXE и передадим его в DLL, которая потом будет удалять объект из динамической памяти, то она вызовет operator delete (или другой заданный deleter), который находится в EXE! Это спасет нас от головной боли с несколькими копиями CRT (одна в EXE, другая в DLL), которая была бы если бы мы передали простой указатель, а не shared_ptr.
Правда возникает вопрос: не возникнет ли у shared_ptr проблем с подсчетом ссылок при пересечении границы EXE-DLL?
Clever shared_ptr
Столкнулся с разработкой классов, подобных следующему:
Суть такова: передаем конструктору класса указатель на некоторый объект, он запоминает его и использует в своих целях.
Если мы динамически создали такой объект специально для передачи его в такой конструктор, то неплохо бы, чтобы деструктор этого класса автоматически удалил его из динамической памяти:
Однако такой вариант предполагает, что мы обязательно будем создавать объекты типа T динамически, и экземпляр класса SomeClass будет владеть ими. Что не позволит:
1. передавать ссылки на объекты, которыми владеет кто-либо другой:
2. использовать конструктор копирования и оператор присваивания, генерируемый компилятором по умолчанию (т.к. при копировании auto_ptr "донор" теряет ссылку).
Второй пункт можно обойти заменой auto_ptr на shared_ptr. Тем же самым методом можно обойти и первый пункт, если добавить немного кода:
Плюс такого решения еще в том, что можно помимо двух крайностей "владеет"/"не владеет", наш класс получает третью возможность - "совладеет":
Минус этого решение в том, что shared_ptr имеет много оверхеда (как по использованию памяти, так по процессорному времени) по сравнению с написанием специализированного класса, поддерживающего только два состояния: "владеет"/"не владеет".
class SomeClass
{
public:
SomeClass(T* Pointer, ...)
: _Pointer(Pointer) { }
private:
T* _Pointer;
}
Суть такова: передаем конструктору класса указатель на некоторый объект, он запоминает его и использует в своих целях.
Если мы динамически создали такой объект специально для передачи его в такой конструктор, то неплохо бы, чтобы деструктор этого класса автоматически удалил его из динамической памяти:
class SomeClass
{
public:
SomeClass(T* Pointer, ...)
: _Pointer(Pointer) { }
private:
auto_ptr<T> _Pointer;
}
SomeClass SomeInstance(new T(), ...);
Однако такой вариант предполагает, что мы обязательно будем создавать объекты типа T динамически, и экземпляр класса SomeClass будет владеть ими. Что не позволит:
1. передавать ссылки на объекты, которыми владеет кто-либо другой:
T Array[3];
SomeClass SomeInstance(&Array[2], ...);
2. использовать конструктор копирования и оператор присваивания, генерируемый компилятором по умолчанию (т.к. при копировании auto_ptr "донор" теряет ссылку).
Второй пункт можно обойти заменой auto_ptr на shared_ptr. Тем же самым методом можно обойти и первый пункт, если добавить немного кода:
struct NullDeleter
{
void operator()(const void *p) { /* ничего не делаем */ }
};
class SomeClass
{
public:
SomeClass(shared_ptr<T> Pointer, ...)
: _Pointer(Pointer) { }
private:
shared_ptr<T> _Pointer;
}
// передаем владение
SomeClass SomeInstance(new T(), ...);
// владение не передаем
T Array[3];
SomeClass SomeInstance(shared_ptr<T>(&Array[2], NullDeleter(), ...);
Плюс такого решения еще в том, что можно помимо двух крайностей "владеет"/"не владеет", наш класс получает третью возможность - "совладеет":
vector<shared_ptr<T> > v;
v.insert(new T());
...
SomeClass SomeInstance(v[0], ...);
...
v.clear();
Минус этого решение в том, что shared_ptr имеет много оверхеда (как по использованию памяти, так по процессорному времени) по сравнению с написанием специализированного класса, поддерживающего только два состояния: "владеет"/"не владеет".
17 апреля 2006
Текстовые файлы в 21 веке
Стандартная библиотека С++ имеет хороший инструментарий для работы с текстовыми файлами. Однако, этот инструментарий немного устарел.
Вот пример кода, написанного с использованием стандартной библиотеки:
На первый взгляд все замечательно, но только на первый. В реальности и в имени файла, и в самом тексте могут встретиться символы, не входящие в стандартный ASCII алфавит.
А проблема в приведенном фрагменте кода в двух местах:
1. const char *filename
2. std::string s;
С первым разобраться достаточно просто:
Казалось бы – и второе достаточно просто решается, заменой ifstream на wifstream. Однако, не все так просто как кажется. А загвоздка вот в чем – текстовые файлы в unicode могут быть в различных кодировках: UTF-8, UTF-16, ... Да и в принципе могут встретиться «старые добрые» файлы в ANSI кодировке.
Как и следовало полагать, «велосипед» по чтению unicode-файлов уже давно изобрели. Вообще, я ожидал его увидеть в boost-е, но его там не оказалось. Я нашел его... в contributes к boost-у. Причем эта библиотека датирована 2003 годом, но так и не попала в boost!
Библиотека отлично работает, сама разпознает кодировку по BOM. Самое интересное – в ней сразу нашлась небольшая ошибка:
Присвоение переменной facet значения идет только в случаях, если выполняется один из case-ов, в любых других случаях она остается неинициализированной, о чем мне и сообщил VC 7.1. Вылечилось просто:
Использование библиотеки очень простое:
Только возникают небольшая (легко устранимая) проблема с интерпретацией конца строки в файле, открытом в режиме binary (CR LF уже не становится автоматически LF).
Вызывает сомнения то, что библиотека с 2003 года не обновлялась и так и не включилась в boost, да еще эта ошибка... Поиск в конференциях показал, что используют эту библиотеку очень редко. Значит используют что-то другое? Скорее всего тяжеловесную ICU.
Вот пример кода, написанного с использованием стандартной библиотеки:
void process_file(const char *filename)
{
std::ifstream f(filename);
std::string s;
while (!f.eof())
{
std::getline(f, s);
process_line(s);
}
f.close();
}
На первый взгляд все замечательно, но только на первый. В реальности и в имени файла, и в самом тексте могут встретиться символы, не входящие в стандартный ASCII алфавит.
А проблема в приведенном фрагменте кода в двух местах:
1. const char *filename
2. std::string s;
С первым разобраться достаточно просто:
void process_file(const wchar_t *filename)
{
std::ifstream f(_wfopen(filename, L"rb"));
...
}
Казалось бы – и второе достаточно просто решается, заменой ifstream на wifstream. Однако, не все так просто как кажется. А загвоздка вот в чем – текстовые файлы в unicode могут быть в различных кодировках: UTF-8, UTF-16, ... Да и в принципе могут встретиться «старые добрые» файлы в ANSI кодировке.
Как и следовало полагать, «велосипед» по чтению unicode-файлов уже давно изобрели. Вообще, я ожидал его увидеть в boost-е, но его там не оказалось. Я нашел его... в contributes к boost-у. Причем эта библиотека датирована 2003 годом, но так и не попала в boost!
Библиотека отлично работает, сама разпознает кодировку по BOM. Самое интересное – в ней сразу нашлась небольшая ошибка:
facet_type* facet;
switch(is.get())
{
case ...:
case ...:
}
if(!facet)
{
...
}
return facet;
Присвоение переменной facet значения идет только в случаях, если выполняется один из case-ов, в любых других случаях она остается неинициализированной, о чем мне и сообщил VC 7.1. Вылечилось просто:
facet_type* facet = NULL;
Использование библиотеки очень простое:
std::wifstream input_file(file_name, std::ios_base::binary);
// autodetect the UTF encoding of this file
boost::utf::imbue_detect_from_bom(input_file);
// some reading
std::wstring s;
input_file >> s;
Только возникают небольшая (легко устранимая) проблема с интерпретацией конца строки в файле, открытом в режиме binary (CR LF уже не становится автоматически LF).
Вызывает сомнения то, что библиотека с 2003 года не обновлялась и так и не включилась в boost, да еще эта ошибка... Поиск в конференциях показал, что используют эту библиотеку очень редко. Значит используют что-то другое? Скорее всего тяжеловесную ICU.
10 апреля 2006
AlphabeticalLess
А теперь, если кому интересно, вариант предиката less для регистронезависимого сравнения строк - для использования в std::map, std::set и т.п.:
Приходится все переводить в upper case, а то иначе получается, что "HELLO" < "hello".
Для сортировки сойдет и версия из предыдущего поста, без to_upper.
struct AlphabeticalLess
{
template <typename CharType>
bool operator ()
(
basic_string<CharType> lhs,
basic_string<CharType> rhs
) const
{
boost::to_upper( lhs );
boost::to_upper( rhs );
return std::use_facet< std::collate< CharType > >( std::locale() )
.compare( lhs.data(), lhs.data() + lhs.size(),
rhs.data(), rhs.data() + rhs.size() ) == -1;
}
};
Приходится все переводить в upper case, а то иначе получается, что "HELLO" < "hello".
Для сортировки сойдет и версия из предыдущего поста, без to_upper.
Сортировка строк по алфавиту
Задача:
отсортировать контейнер строк по алфавиту.
Попытка 1:
Результат получаем сортированный. Но не по алфавиту. std::sort по умолчанию использует для сортировки предикат less<T>, который в свою очередь использует operator<() для типа T, и если у нас строки типа basic_string<Ch>, то operator<() сравнивает посимвольно две строки, считая их значениями типа Ch, а не символами алфавита.
Единственное, кто может знать про понятие "алфавит", это локаль.
Попытка 2:
Вот теперь сортирует как надо.
Используемый мною тест:
Update: хорошая статья на эту тему - How To Do Case-Insensitive String Comparison
отсортировать контейнер строк по алфавиту.
Попытка 1:
std::sort( container.begin(), container.end() );
Результат получаем сортированный. Но не по алфавиту. std::sort по умолчанию использует для сортировки предикат less<T>, который в свою очередь использует operator<() для типа T, и если у нас строки типа basic_string<Ch>, то operator<() сравнивает посимвольно две строки, считая их значениями типа Ch, а не символами алфавита.
Единственное, кто может знать про понятие "алфавит", это локаль.
Попытка 2:
struct AlphabeticalCompare
{
template <typename CharType>
bool operator ()(const basic_string<CharType> & lhs, const basic_string<CharType> & rhs) const
{
return std::use_facet< std::collate< CharType > >( std::locale() )
.compare( lhs.data(), lhs.data() + lhs.size(),
rhs.data(), rhs.data() + rhs.size() ) == -1;
}
};
std::sort(container.begin(), container.end(), AlphabeticalCompare());
Вот теперь сортирует как надо.
Используемый мною тест:
#include <iostream>
#include <string>
#include <locale>
#include <vector>
#include <algorithm>
struct AlphabeticalCompare
{
template <typename CharType>
bool operator ()(const basic_string<CharType> & lhs, const basic_string<CharType> & rhs) const
{
return std::use_facet< std::collate< CharType > >( std::locale() )
.compare( lhs.data(), lhs.data() + lhs.size(),
rhs.data(), rhs.data() + rhs.size() ) == -1;
}
};
int main()
{
std::locale::global(std::locale("Ukrainian_Ukraine.1251"));
std::vector< std::wstring > container;
std::wstring s1; s1 += 0x456; // cyrillic "л"
std::wstring s2; s2 += 0x43b; // cyrillic "і"
// 0x456 is greater than 0x43b, but "і" comes before "л" in Ukrainian
container.push_back(s1);
container.push_back(s2);
std::sort(container.begin(), container.end(), AlphabeticalCompare());
std::cout << (container[0] == s1 ? "passed" : "failed") << std::endl;
return 0;
}
Update: хорошая статья на эту тему - How To Do Case-Insensitive String Comparison
08 апреля 2006
try-блоки конструкторов
Читаю Саттера. Оказывается у конструкторов можно писать try-блоки для перехвата исключений, генерируемых в конструкторах member-ов и родительских классов:
Такая методика позволяет конвертировать сгенерированные исключения в какие-нибудь другие исключения. Интересно, эту возможность вообще кто-нибудь использует?
class C : private A
{
B b_;
};
C::C()
try
: A ( /*...*/ ) // optional initialization list
, b_( /*...*/ )
{
}
catch( ... )
{
// We get here if either A::A() or B::B() throws.
// If A::A() succeeds and then B::B() throws, the
// language guarantees that A::~A() will be called
// to destroy the already-created A base subobject
// before control reaches this catch block.
}
Такая методика позволяет конвертировать сгенерированные исключения в какие-нибудь другие исключения. Интересно, эту возможность вообще кто-нибудь использует?
28 марта 2006
Фабрики объектов v.2
Немного переделал шаблон фабрик так, чтобы можно было использовать executor-ы с произвольным количеством параметров. Вообще, получилась даже не столько именно "фабрика", сколько контейнер-singleton для регистрации произвольных объектов, не обязательно creator-ов или executor-ов.
Немного изменился синтаксис:
Немного изменился синтаксис:
typedef CFactory<IdType, ReturnType(ParameterType1, ParameterType2, ...)> SomeFactory;
...
SomeFactory::Execute(Id)(Parameter1, Parameter2, ...)
25 марта 2006
Object Factory
Задача
В проекте, над которым я сейчас работаю, постоянно стали требоваться так называемые "фабрики объектов" - глобальные объекты, создающие экземпляры каких-либо классов по указанию определенного идентификатора. Например, бизнес-логика, генерирующая отчеты. На входе имеем ID отчета, который нужно создать, и параметры отчета - на выходе нужно получить сформированный отчет. Причем в виде switch (ReportID) { ... } это реализовывать неудобно - по мере добавления отчетов будет расти количество case и все они собраны в одном месте, то есть нужно каждый раз перекомпилировать этот switch... Идеальное решение, чтобы можно было написать реализацию определенного отчета в отдельном модуле (скажем, файле исходного текста), и чтобы он сам "зарегистрировался" в фабрике отчетов под своим ID. Тогда запросив у фабрики отчет по ID, она сама найдет "создателя" нужного отчета.
Решение
Я попытался общий создать шаблон для подобных фабрик объектов. В частном случае от фабрики требуется вызвать функцию, зарегистрированную для определенного ID типа IdType. Функция принимает в качестве параметра объект типа ParameterType, а возвращает значение типа ReturnType.
Например, для отчетов может быть IdType = string, ParameterType = map<string, _variant_t>, ReturnType = shared_ptr<Report>. То есть отчет идентифицируем по его названию, передаем ему параметры, где каждый параметр = имя (string) + значение (_variant_t), а получаем – умный указатель на созданный отчет.
Шаблон я создал следующий: Factory.h
Пример использования:
Нюансы
В случае, если все генераторы отчетов компилируются все в одном месте с самой фабрикой (то есть не разнесены по разным библиотекам), то регистрация на фабрике выполняется достаточно просто – введением глобальной переменной:
В случае, если такую конструкцию разместить в отдельной библиотеке, то ликновщик не прилинкует ее, так как на нее не будет явных ссылок. В тако случае приходится прописывать явно:
В библиотеке:
В основном модуле (исполнимый файл или DLL):
В проекте, над которым я сейчас работаю, постоянно стали требоваться так называемые "фабрики объектов" - глобальные объекты, создающие экземпляры каких-либо классов по указанию определенного идентификатора. Например, бизнес-логика, генерирующая отчеты. На входе имеем ID отчета, который нужно создать, и параметры отчета - на выходе нужно получить сформированный отчет. Причем в виде switch (ReportID) { ... } это реализовывать неудобно - по мере добавления отчетов будет расти количество case и все они собраны в одном месте, то есть нужно каждый раз перекомпилировать этот switch... Идеальное решение, чтобы можно было написать реализацию определенного отчета в отдельном модуле (скажем, файле исходного текста), и чтобы он сам "зарегистрировался" в фабрике отчетов под своим ID. Тогда запросив у фабрики отчет по ID, она сама найдет "создателя" нужного отчета.
Решение
Я попытался общий создать шаблон для подобных фабрик объектов. В частном случае от фабрики требуется вызвать функцию, зарегистрированную для определенного ID типа IdType. Функция принимает в качестве параметра объект типа ParameterType, а возвращает значение типа ReturnType.
Например, для отчетов может быть IdType = string, ParameterType = map<string, _variant_t>, ReturnType = shared_ptr<Report>. То есть отчет идентифицируем по его названию, передаем ему параметры, где каждый параметр = имя (string) + значение (_variant_t), а получаем – умный указатель на созданный отчет.
Шаблон я создал следующий: Factory.h
Пример использования:
typedef CFactory
<const string &, shared_ptr<Report>, const map<string, _variant_t> & >
CReportFactory;
shared_ptr<report> Generator(const map<string, _variant_t> &)
{
// generator code
}
// register Generator as "test report"
bool Registered = CReportFactory::Register("test report", Generator);
int main()
{
// fill parameters
map<string, _variant_t> Parameters;
Parameters.insert(...);
// generate report
shared_ptr<Report> TestReport =
CReportFactory::Execute("test report", Parameters);
}
Нюансы
В случае, если все генераторы отчетов компилируются все в одном месте с самой фабрикой (то есть не разнесены по разным библиотекам), то регистрация на фабрике выполняется достаточно просто – введением глобальной переменной:
bool SomeGeneratorRegistered = SomeFactory.Register(SomeID, SomeGenerator);
В случае, если такую конструкцию разместить в отдельной библиотеке, то ликновщик не прилинкует ее, так как на нее не будет явных ссылок. В тако случае приходится прописывать явно:
В библиотеке:
bool SomeLibrary1Registered =
SomeGenerator11Registered &&
SomeGenerator12Registered &&
...;
В основном модуле (исполнимый файл или DLL):
extern bool SomeLibrary1Registered;
extern bool SomeLibrary2Registered;
...
bool LibrariesRegistered =
SomeLibrary1Registered &&
SomeLibrary2Registered &&
...;
12 марта 2006
decimal vs float
В C++ если написать 12, то это будет воспринято компилятором как (int)12, а 12.0 дает (float)12.
MS SQL Server 2000 понимает 12 как int, а 12.0 - ...нет, не как float, а как decimal с шестью знаками после запятой.
Мне нужно было посчитать коэффициент 1/12, чтобы результат получился float. 1/12 дает 0, и я использовал 1.0/12.0 наивно полагая, что числа будут восприняты как float. Ан нет, при выполнении запросов вылезали погрешности. Пришлось написать так: 1e0/0.12e2 (или, как вариант - 1e0/12).
MS SQL Server 2000 понимает 12 как int, а 12.0 - ...нет, не как float, а как decimal с шестью знаками после запятой.
Мне нужно было посчитать коэффициент 1/12, чтобы результат получился float. 1/12 дает 0, и я использовал 1.0/12.0 наивно полагая, что числа будут восприняты как float. Ан нет, при выполнении запросов вылезали погрешности. Пришлось написать так: 1e0/0.12e2 (или, как вариант - 1e0/12).
07 марта 2006
deque vs vector
Во всех книжках встречаю "если не знаете какой контейнер использовать - используйте vector". Мне вот что-то не взлюбился этот vector, я вот deque люблю. Точнее ее реализацию, где она реализована в виде страниц и вектора указателей на эти страницы. В дальнейшем буду рассматривать только деку с такой реализацией.
Преимущества deque перед vector:
1. Меньше overhead. Вектор растет сразу в два раза, а дека - фиксированными страницами. Если храним мегабайт с копейкой, то вектор будет двухмегабайтный, а дека - мегабайт плюс маленький хвостик.
2. Отстуствие копирования старого содержимого на новое место при расширении контейнера.
Недостатки:
1. Отсутствие доступа к содержимому как к линейному массиву.
Инкремент-декремент итераторов у деки по скорости сравнимы с векторными, произвольный доступ к элементам - аналогично.
А вот отсутствие ненужного копирования при realloc-е у деки существенно улучшает performance.
Достоинство вектора - доступ к содержимому как к линейному массиву. То есть можно накопить кучу данных в векторе, а потом передать все содержимое какой-нибудь API-шной функции типа WriteFile или SendData с параметрами (void *buf, size_t size).
Но это является плюсом вектора только в теории. Сравним два варианта:
1. Накапливаем данные в vector, затем за один вызов "сливаем" их в нашу WriteFile(void *buf, size_t size)
2. Накапливаем данные в deque, затем в цикле "сливаем" часть в буфер ограниченного размера, а потом передаем этот буфер нашей WriteFile.
Предполагаем, что изначально мы не знаем сколько элементов придется поместить в контейнер (иначе, естественно, vector::reserve() всех порвёт).
Пусть в результате в контейнер окажутся помещенными N элементов. Ближайшие к N степени двойки назовем 2n и 2n+1. То есть 2n < N <= 2n+1.
В первом случае на этапе накопления произойдет 1+2+4+8+...+2n = 2n+1-1 копирований. Назовем это количество С1.
Во втором случае будет копирований элементов ровно столько, сколько элементов в контейнере. Происходить все они будут в цикле (когда все они будут копироваться в буфер), а при накоплении данных дек ничего не перекладывает с одного места в другое. Количество копирований C2=N.
С1=2n+1-1 всегда больше С2=N, кроме двух случаев, которые можно особо не учитывать. А если предположить, что в среднем N где-то посередине межде 2n и 2n+1, то второй вариант на 25% быстрее первого.
То есть даже тот факт, что вектор потом можно удобно (без дополнительных копирований) "слить" API-шной функции, не дал вектору преимуществ перед декой.
Исключения: при маленьких N или в случаях, когда N заранее известно - тут вектор выигрывает.
Преимущества deque перед vector:
2. Отстуствие копирования старого содержимого на новое место при расширении контейнера.
Недостатки:
1. Отсутствие доступа к содержимому как к линейному массиву.
Инкремент-декремент итераторов у деки по скорости сравнимы с векторными, произвольный доступ к элементам - аналогично.
А вот отсутствие ненужного копирования при realloc-е у деки существенно улучшает performance.
Достоинство вектора - доступ к содержимому как к линейному массиву. То есть можно накопить кучу данных в векторе, а потом передать все содержимое какой-нибудь API-шной функции типа WriteFile или SendData с параметрами (void *buf, size_t size).
Но это является плюсом вектора только в теории. Сравним два варианта:
1. Накапливаем данные в vector, затем за один вызов "сливаем" их в нашу WriteFile(void *buf, size_t size)
2. Накапливаем данные в deque, затем в цикле "сливаем" часть в буфер ограниченного размера, а потом передаем этот буфер нашей WriteFile.
Предполагаем, что изначально мы не знаем сколько элементов придется поместить в контейнер (иначе, естественно, vector::reserve() всех порвёт).
Пусть в результате в контейнер окажутся помещенными N элементов. Ближайшие к N степени двойки назовем 2n и 2n+1. То есть 2n < N <= 2n+1.
В первом случае на этапе накопления произойдет 1+2+4+8+...+2n = 2n+1-1 копирований. Назовем это количество С1.
Во втором случае будет копирований элементов ровно столько, сколько элементов в контейнере. Происходить все они будут в цикле (когда все они будут копироваться в буфер), а при накоплении данных дек ничего не перекладывает с одного места в другое. Количество копирований C2=N.
С1=2n+1-1 всегда больше С2=N, кроме двух случаев, которые можно особо не учитывать. А если предположить, что в среднем N где-то посередине межде 2n и 2n+1, то второй вариант на 25% быстрее первого.
То есть даже тот факт, что вектор потом можно удобно (без дополнительных копирований) "слить" API-шной функции, не дал вектору преимуществ перед декой.
Исключения: при маленьких N или в случаях, когда N заранее известно - тут вектор выигрывает.
24 февраля 2006
Велосипеды
Как часто, однако, делаешь что-либо, а потом понимаешь, что изобретаешь велосипед. Недавно изобрел один такой.
Некие объекты имели контейнеры. Но не контейнеры объектов, а контейнеры указателей. И, как водится, перед удалением этих контейнера в деструкторе был подобный код:
И вот только тогда, когда все это реализовал, подумал - ведь не я первый с этой проблемой сталкиваюсь - контейнеры указателей. Раз их в стандартной библиотеке нет, то видимо они долны быть в Boost. Немного полистав документацию Boost, я нашел то, что было нужно - Pointer Container Library.
Некие объекты имели контейнеры. Но не контейнеры объектов, а контейнеры указателей. И, как водится, перед удалением этих контейнера в деструкторе был подобный код:
CTableContainer::iterator TableIt;Недолго думая, прикрутил к контейнерам (через шаблон) деструктор, который все это делает сам. Получились такие "умные контейнеры указателей" - сами удаляют за собой свои объекты.
for (TableIt = Tables.begin();
TableIt != Tables.end();
TableIt++)
{
delete *TableIt;
*TableIt = NULL;
}
И вот только тогда, когда все это реализовал, подумал - ведь не я первый с этой проблемой сталкиваюсь - контейнеры указателей. Раз их в стандартной библиотеке нет, то видимо они долны быть в Boost. Немного полистав документацию Boost, я нашел то, что было нужно - Pointer Container Library.
Подписаться на:
Сообщения (Atom)