Что такое traits? - Определение размера файла с помощью класса ifstream
ОГЛАВЛЕНИЕ
Определение размера файла с помощью класса ifstream
Теперь мы знаем основные принципы для работы с traits. Так что давайте рассмотрим пример, который помогает расширить стандартную библиотеку C++.
Давайте рассмотрим такую задачу(перефразировано из вопроса с форума):
Цитата |
Я хочу определить размер файла с помощь класса ifstream. Сам файл весит больше 5 Гб. Функция tellg() возвращает какое-то нереальное значение. Как можно правильно определить размер файла? |
На данный момент все известные мне версии стандартной библиотеки C++ представляют позицию в файле 32-разрядным целым. Но дело в том, что обычные 32-разрядные целые числа не могут представлять размер файла, большего 4 ГБ(происходит переполнение). То есть нам надо каким-либо образом заставить стандартную библиотеку использовать не 32-разрядные числа, а, например 64-разрядные(или вообще наш собственный тип(класс), который мы опишем). Как это сделать? Как вы уже догадались, помогут нам traits.
Как известно, ifstream - это только typedef от класса basic_ifstream. Сам же класс basic_ifstream принимает 2 параметра шаблона: первый из них определяет тип символа, а второй определяет свойста(traits). Так вот эти свойста и должны определять, каким типом представлять позицию в файле, как сравнивать символы и тд. Второй параметр шаблона класса basic_ifstream по умолчанию будет классом char_traits. Это стандартный класс, который описывает основные свойста: нужные типы, как сравнивать символы, присваивать и тд.. Так как мы не собираемся переопределять это все(нам надо заменить только 2 типа), тогда хорошей идеей будет унаследоваться от класса char_traits.
У класса char_traits есть 2 интересующих нас типа(полный список типов можно найти в документации):
- pos_type - тип, используемый для представления позиции в потоке
- off_type - тип, используемый для представления смещений между позициями в потоке
Вот их-то как раз нам и надо переопределить. Давайте сделаем первую попытку:
template <typename char_t>
struct long_pointer_traits : public std::char_traits<char_t> {
typedef __int64 pos_type;
typedef __int64 off_type;
};
typedef std::basic_ifstream<char, long_pointer_traits<char> > long_ifstream;
// используем long_ifstream
Но вот незадача: этот код не компилируется. Дело в том, что pos_type должен уметь конструироваться из нескольких заранее определенных типов(как показало исследование, 2). Базовые типы этого делать не умеют, так что придется написать свой собственный класс. Я не буду заострять внимание на этом классе, так как статья немного не на эту тему. Я просто приведу реализацию этого класса, а если у вас будут какие-то вопросы, то писать либо здесь, либо в PM. Итак, вот код:
// пространство имен, в которое заносятся детали реализацииОК, теперь все компилируется и работает. Но кроме получения позиции в файле, нам обычно надо работать еще с этими файлами(читать, писать). И, конечно, нам приходится работать со строками. Тогда если мы попытаемся считать строку из файла таким образом:
namespace detail {
template <typename num_type, typename state_type = std::mbstate_t>
class pos_type_t {
typedef pos_type_t<num_type, state_type> my_type;
num_type m_pos;
state_type m_state;
static state_type initial_state;
public:
// конструкторы
pos_type_t(std::streampos off) : m_pos(off), m_state(initial_state) {}
pos_type_t(num_type off = 0) : m_pos(off), m_state(initial_state) {}
pos_type_t(state_type state, num_type pos) : m_pos(pos), m_state(state) {}
// получение состояния потока
state_type state() const {
return(m_state);
}
// установка состояния потока
void state(state_type st) {
m_state = st;
}
// получение позиции
num_type seekpos() const {
return(m_pos);
}
// оператор преобразования
operator num_type() const {
return(m_pos);
}
// далее идут операторы, которые осуществляют арифметические операции
num_type operator- (const my_type& rhs) const {
return(static_cast<num_type>(*this) - static_cast<num_type>(rhs));
}
my_type& operator+= (num_type pos) {
m_pos += pos;
return(*this);
}
my_type& operator-= (num_type pos) {
m_pos -= pos;
return(*this);
}
my_type operator+ (num_type pos) const {
my_type tmp(*this);
return(tmp += pos);
}
my_type operator- (num_type pos) const {
my_type tmp(*this);
return(tmp -= pos);
}
// операторы сравнения
bool operator== (const my_type& rhs) const {
return(static_cast<num_type>(*this) == static_cast<num_type>(rhs));
}
bool operator!= (const my_type& rhs) const {
return(!(*this == rhs));
}
};
//---------------------------------------------------
// статическая константа, которая обозначает начальное состояние
template <typename num_type, typename state_type>
state_type pos_type_t<num_type, state_type>::initial_state;
}
//---------------------------------------------------
// наконец-то наш класс свойств:
template <typename char_t, typename long_pos_t>
struct long_pointer_traits : public std::char_traits<char_t> {
// определение pos_type через наш только что написанный класс
typedef detail::pos_type_t<long_pos_t> pos_type;
// определение off_type через тип, переданный во 2 аргументе шаблона
typedef long_pos_t off_type;
};
//---------------------------------------------------
// вводим тип "длинного" файла
typedef std::basic_ifstream<char, long_pointer_traits<char, __int64> > long_ifstream;
// используем long_ifstream для получения размера файла
long_ifstream infile(strFileName, std::ios::binary);
std::string res;
std::getline(infile, res);
То мы получим ошибку компиляции. Проблема в том, что std::string - это "всего лишь" typedef от std::basic_string. Этот класс принимает 2 параметра шаблона: первый - тип для представления символа, а второй(как вы уже, наверное, догадались) - traits. Так вот, для корректной работы нам надо определить и свой тип строки:
// "длинные" типы:
typedef std::basic_ifstream<char, long_pointer_traits<char, __int64> > long_ifstream;
typedef std::basic_string<char, long_pointer_traits<char, __int64> > long_string;
long_ifstream infile(strFileName, std::ios::binary);
long_string res;
std::getline(infile, res);
Теперь все работает прекрасно. Таким образом, для правильного взаимодействия компонентов стандартной библиотеки нам придется определять нужные типы и работать с ними. К сожалению, на данный момент я не знаю способа, как можно было бы создать нужный тип для стандартных потоков ввода/вывода(cin, cout, cerr, clog). Так что чтобы вывести такую "длинную" строку на экран, надо будет написать свой оператор вывода такой строки. Другого решения мне неизвестно(если кто-то знает - поделитесь, буду признателен).
Также хочу сказать несколько слов о совместимости и переносимости: приведенный мной код по определению размера большого файла был проверен на компиляторах VC7.1 и Intel C++ 8.0. Использовалась стандартная библиотека, которая идет по умолчанию с VC. При работе с ней замечено никаких ошибок не было. Проверялся код и с использованием STLPort версий 4.6.2 и 5.0. Компилировался он без проблем, но работал неправильно. Надеюсь, в дальнейших версиях STLPort'а это будет исправлено и работать будет корректно, так как данный код соответствует стандарту.