На основе комментариев и ответов, кажется, есть три подхода:
- Написать собственную версию
getline()
возможно с помощью std::istream::getline()
члена внутри, чтобы получить фактические символы.
- Используйте буфер потока фильтрации, чтобы ограничить объем потенциально полученных данных.
- Вместо чтения
std::string
используйте строковое создание экземпляра с помощью специального распределителя, ограничивающего объем памяти, хранящейся в строке.
Не все предложения поставляются с кодом. Этот ответ дает код для всех подходов и немного обсуждения всех трех подходов. Прежде чем вдаваться в подробность реализации это первый стоит отметить, что есть несколько вариантов того, что должно произойти, если чрезмерно длинный вход получен:
- Чтение сверхдолгой линии может привести к успешному чтению частичной линии, т.е., результирующая строка содержит прочитанный контент, и в потоке не установлены флаги ошибок. Это означает, однако, что невозможно отличить линию, попадающую точно в лимит или слишком длинную. Так как предел несколько произволен, в любом случае, это, вероятно, не имеет особого значения.
- Чтение строки с перекрытием может считаться сбоем (т. Е. Установка
std::ios_base::failbit
и/или std::ios_base::bad_bit
), и, поскольку чтение не выполнено, введите пустую строку. Очевидно, что выход пустой строки предотвращает потенциальный просмотр строки, прочитанной до сих пор, чтобы увидеть, что происходит.
- Чтение строки с перекрытием может обеспечить частичную прочтение строки, а также установить флаги ошибок в потоке. Это кажется разумным поведением как для обнаружения того, что что-то есть, так и для ввода информации для потенциального контроля.
Хотя есть несколько примеров кода, в которых реализована ограниченная версия getline()
, вот еще одна! Я думаю, что это проще (хотя, возможно, медленнее, производительность может быть решена при необходимости), который также сохраняет это std::getline()
с Интерфейс: его использование ручья width()
общаться предел (возможно, принимая width()
во внимание разумное расширение std::getline()
):
template <typename cT, typename Traits, typename Alloc>
std::basic_istream<cT, Traits>&
safe_getline(std::basic_istream<cT, Traits>& in,
std::basic_string<cT, Traits, Alloc>& value,
cT delim)
{
typedef std::basic_string<cT, Traits, Alloc> string_type;
typedef typename string_type::size_type size_type;
typename std::basic_istream<cT, Traits>::sentry cerberos(in);
if (cerberos) {
value.clear();
size_type width(in.width(0));
if (width == 0) {
width = std::numeric_limits<size_type>::max();
}
std::istreambuf_iterator<char> it(in), end;
for (; value.size() != width && it != end; ++it) {
if (!Traits::eq(delim, *it)) {
value.push_back(*it);
}
else {
++it;
break;
}
}
if (value.size() == width) {
in.setstate(std::ios_base::failbit);
}
}
return in;
}
Эта версия getline()
используется так же, как std::getline()
, но когда это представляется разумным ограничить количество данных прочитан, width()
установлен, например:
std::string line;
if (safe_getline(in >> std::setw(max_characters), line)) {
// do something with the input
}
Другой подход просто использовать буфер потока фильтрации, чтобы ограничить количество ввода: фильтр будет просто подсчитывать количество обработанных символов и ограничивать количество подходящим количеством символов. Этот подход на самом деле проще применять ко всему потоку, чем к отдельной строке: при обработке только одной строки фильтр не может просто получать буферы, заполненные символами из базового потока, потому что нет надежного способа вернуть символы. Реализация небуферизованных версии по-прежнему просто, но, вероятно, не особенно эффективно:
template <typename cT, typename Traits = std::char_traits<char> >
class basic_limitbuf
: std::basic_streambuf <cT, Traits> {
public:
typedef Traits traits_type;
typedef typename Traits::int_type int_type;
private:
std::streamsize size;
std::streamsize max;
std::basic_istream<cT, Traits>* stream;
std::basic_streambuf<cT, Traits>* sbuf;
int_type underflow() {
if (this->size < this->max) {
return this->sbuf->sgetc();
}
else {
this->stream->setstate(std::ios_base::failbit);
return traits_type::eof();
}
}
int_type uflow() {
if (this->size < this->max) {
++this->size;
return this->sbuf->sbumpc();
}
else {
this->stream->setstate(std::ios_base::failbit);
return traits_type::eof();
}
}
public:
basic_limitbuf(std::streamsize max,
std::basic_istream<cT, Traits>& stream)
: size()
, max(max)
, stream(&stream)
, sbuf(this->stream->rdbuf(this)) {
}
~basic_limitbuf() {
std::ios_base::iostate state = this->stream->rdstate();
this->stream->rdbuf(this->sbuf);
this->stream->setstate(state);
}
};
Этот буфер поток уже настроен для вставки себя на строительство и удалить себя от разрушения. То есть, он может быть использован просто как это:
std::string line;
basic_limitbuf<char> sbuf(max_characters, in);
if (std::getline(in, line)) {
// do something with the input
}
Было бы легко добавить манипулятор настройку до предела, тоже. Одно из преимуществ этого подхода заключается в том, что ни один из кода чтения не должен быть затронут, если общий размер потока может быть ограничен: фильтр можно настроить сразу после создания потока. Когда нет необходимости отпирать фильтр, фильтр может также использовать буфер, который значительно повысит производительность.
Третий подход заключается в использовании std::basic_string
с пользовательским распределителем. Есть два аспекта, которые немного неуклюжими о распределителе подходе:
- Строка читается на самом деле имеет тип, который не является непосредственно конвертируется в
std::string
(хотя это и не трудно сделать преобразование).
- Максимальный размер массива можно легко ограничить, но строка будет иметь более или менее случайный размер, меньший, чем это: при сбое потока выделение исключения генерируется, и нет попытки вырастить строку меньшим размером.
Вот необходимый код для распределителя ограничения размера выделенного:
template <typename T>
struct limit_alloc
{
private:
std::size_t max_;
public:
typedef T value_type;
limit_alloc(std::size_t max): max_(max) {}
template <typename S>
limit_alloc(limit_alloc<S> const& other): max_(other.max()) {}
std::size_t max() const { return this->max_; }
T* allocate(std::size_t size) {
return size <= max_
? static_cast<T*>(operator new[](size))
: throw std::bad_alloc();
}
void deallocate(void* ptr, std::size_t) {
return operator delete[](ptr);
}
};
template <typename T0, typename T1>
bool operator== (limit_alloc<T0> const& a0, limit_alloc<T1> const& a1) {
return a0.max() == a1.max();
}
template <typename T0, typename T1>
bool operator!= (limit_alloc<T0> const& a0, limit_alloc<T1> const& a1) {
return !(a0 == a1);
}
Распределитель будет использоваться что-то вроде этого (кода компилируется нормально с последней версией clang, но не с gcc) :
std::basic_string<char, std::char_traits<char>, limit_alloc<char> >
tmp(limit_alloc<char>(max_chars));
if (std::getline(in, tmp)) {
std::string(tmp.begin(), tmp.end());
// do something with the input
}
В целом, существует несколько подхода каждый со своим собственным небольшим недостатком, но каждый достаточно жизнеспособным для заявленной цели предельного отрицания серва ледовые наложения по линиям с перекрытием:
- Использование пользовательской версии
getline()
означает, что код чтения необходимо изменить.
- Использование настраиваемого буфера потока выполняется медленно, если размер всего потока не ограничен.
- Использование пользовательского распределителя дает меньше контроля и требует некоторых изменений в чтении кода.
Как насчет пользовательского распределителя, который будет бросать, если его попросят выделить за пределами вашего порога? Постройте объект 'basic_string' с помощью этого распределителя и зачитайте его. – Praetorian
Может быть, subclassing std :: string и функция 'max_size()', которая выплевывает что-то маленькое? – Collin
@Praetorian: Я думаю, использование распределителя было бы вариантом. К сожалению, он меняет тип 'std :: string'. –