Може ли да се използва std::basic_string за прилагане на низ с ограничение на дължината?

Работя с API на ниско ниво, който приема char* и числова стойност за представяне на низ и съответно неговата дължина. Моят код използва std::basic_string и извиква тези методи с подходящия превод. За съжаление, много от тези методи приемат дължини на низове с различен размер (т.е. max(unsigned char), max(short) и т.н...) и аз съм заседнал да пиша код, за да се уверя, че моите екземпляри на низове не надвишават предписаната максимална дължина чрез API на ниско ниво.

По подразбиране максималната дължина на екземпляр std::basic_string е ограничена от максималната стойност на size_t (или max(unsigned int), или max(__int64)). Има ли начин да манипулирам характеристиките и реализациите на разпределителя на std::basic_string имплементация, така че да мога да посоча моя собствен тип, който да използвам вместо size_t? По този начин се надявам да се възползвам от всички съществуващи проверки на границите в изпълнението на std::basic_string, така че да не се налага да го правя, когато извършвам превода.

Първоначалното ми проучване предполага, че това не е възможно без да напиша собствен клас низове, но се надявам, че съм пропуснал нещо :)


person Steve Guidi    schedule 19.10.2009    source източник


Отговори (3)


можете да подадете персонализиран разпределител на std::basic_string, който има максимален размер, какъвто искате. Това трябва да е достатъчно. Може би нещо подобно:

template <class T>
class my_allocator {
public:
    typedef T              value_type;

    typedef std::size_t    size_type;
    typedef std::ptrdiff_t difference_type;
    typedef T*             pointer;
    typedef const T*       const_pointer;
    typedef T&             reference;
    typedef const T&       const_reference;

    pointer address(reference r) const             { return &r; }
    const_pointer address(const_reference r) const { return &r; }

    my_allocator() throw() {}

    template <class U>
    my_allocator(const my_allocator<U>&) throw() {}

    ~my_allocator() throw() {}

    pointer allocate(size_type n, void * = 0) {
        // fail if we try to allocate too much
        if((n * sizeof(T))> max_size()) { throw std::bad_alloc(); }
        return static_cast<T *>(::operator new(n * sizeof(T)));
    }

    void deallocate(pointer p, size_type) {
        return ::operator delete(p);
    }

    void construct(pointer p, const T& val) { new(p) T(val); }
    void destroy(pointer p)                 { p->~T(); }

    // max out at about 64k
    size_type max_size() const throw() { return 0xffff; }

    template <class U>
    struct rebind { typedef my_allocator<U> other; };

    template <class U>
    my_allocator& operator=(const my_allocator<U> &rhs) {
        (void)rhs;
        return *this;
    }
};

Тогава вероятно можете да направите това:

typedef std::basic_string<char, std::char_traits<char>, my_allocator<char> > limited_string;

РЕДАКТИРАНЕ: Току-що направих тест, за да се уверя, че това работи според очакванията. Следният код го тества.

int main() {
    limited_string s;
    s = "AAAA";
    s += s;
    s += s;
    s += s;
    s += s;
    s += s;
    s += s;
    s += s; // 512 chars...
    s += s;
    s += s;
    s += s;
    s += s;
    s += s;
    s += s; // 32768 chars...
    s += s; // this will throw std::bad_alloc

    std::cout << s.max_size() << std::endl;
    std::cout << s.size() << std::endl;
}

Това последно s += s ще го постави отгоре и ще предизвика std::bad_alloc изключение (тъй като лимитът ми е малко под 64k). За съжаление имплементацията std::basic_string::max_size() на gcc не базира резултата си на разпределителя, който използвате, така че пак ще твърди, че може да разпредели повече. (Не съм сигурен дали това е грешка или не...).

Но това определено ще ви позволи да наложите твърди ограничения върху размерите на низовете по прост начин. Можете дори да направите максималния размер параметър на шаблон, така че да трябва да напишете кода за разпределителя само веднъж.

person Evan Teran    schedule 19.10.2009
comment
Щях да добавя int шаблонен параметър ‹typename T, int N› и max_size() да връща N; тогава той може да направи typedef std::basic_string‹char, std::char_traits‹char›, my_allocator‹char, 256› › my256string; - person jmucchiello; 20.10.2009
comment
Мисля, че излагането на size_type като шаблонен аргумент би било от полза, тъй като тогава потребителят е свободен да избере кой size_type е най-подходящ за даден екземпляр на низ. Тогава частичната специализация на шаблона ще помогне този тип да работи добре с basic_string. - person Steve Guidi; 20.10.2009

Съгласен съм с Evan Teran за неговото решение. Това вече не е просто модификация на неговото решение:

template <typename Type, typename std::allocator<Type>::size_type maxSize>
struct myalloc : std::allocator<Type>
{
    // hide std::allocator[ max_size() & allocate(...) ]

    std::allocator<Type>::size_type max_size() const throw()
    {
        return maxSize;
    }
    std::allocator<Type>::pointer allocate
        (std::allocator<Type>::size_type n, void * = 0)
    {
        // fail if we try to allocate too much
        if((n * sizeof(Type))> max_size()) { throw std::bad_alloc(); }
        return static_cast<Type *>(::operator new(n * sizeof(Type)));
    }
};

Имайте предвид, че изобщо не трябва да използвате полиморфизъм с myalloc. Така че това е пагубно:

// std::allocator doesn't have a virtual destructor
std::allocator<char>* alloc = new myalloc<char>;

Просто го използвате, сякаш е отделен тип, безопасно е в следния случай:

myalloc<char, 1024> alloc; // max size == 1024
person AraK    schedule 19.10.2009
comment
Да, просто си помислих да направя максималния размер параметър на шаблона. За съжаление, не мисля, че вашето решение ще работи както е. Тъй като (поне gcc's) внедряването на низ не базира своя максимален размер на максималния размер на разпределителя. Така че трябваше да направя allocate(...) хвърляне, ако поиска повече от max_size байта. - person Evan Teran; 20.10.2009
comment
@Evan Вашето решение е хубаво, получихте моите +1 между другото. Ще добавя още код за allocate, както изяснихте :) - person AraK; 20.10.2009

Не можете ли да създадете клас със std::string като родител и да замените c_str()? Или дефинирайте свой собствен c_str16(), c_str32() и т.н. и имплементирайте превод там?

person Zepplock    schedule 19.10.2009
comment
По-голямата част от стандартната библиотека не е предназначена да бъде наследена (те нямат виртуални методи). Така че това не е препоръчително. - person Evan Teran; 20.10.2009