Pode -se aproveitar o std :: Basic_String para implementar uma string com uma limitação de comprimento?
Pergunta
Estou trabalhando com uma API de baixo nível que aceita um char*
e valor numérico para representar uma string e seu comprimento, respectivamente. Meu código usa std::basic_string
e chama esses métodos com a tradução apropriada. Infelizmente, muitos desses métodos aceitam comprimentos de string de tamanho variável (ou seja, max (unsigned char
), Max (short
), etc ...) e estou preso ao escrever um código para garantir que minhas instâncias de string não excedam o comprimento máximo prescrito pela API de baixo nível.
Por padrão, o comprimento máximo de um std::basic_string
a instância está vinculada pelo valor máximo de size_t
(Max (unsigned int
) ou max (__int64
)). Existe uma maneira de manipular as características e implementações de alocadores de um std::basic_string
implementação para que eu possa especificar meu próprio tipo para usar no lugar de size_t
? Ao fazer isso, espero alavancar qualquer cheques de limites existentes dentro do std::basic_string
Implementação para que eu não precise fazer isso ao executar a tradução.
Minha investigação inicial sugere que isso não é possível sem escrever minha própria aula de cordas, mas espero ter esquecido de algo :)
Solução
você pode passar um alocador personalizado para std::basic_string
que tem um tamanho máximo do que você quiser. Isso deve ser suficiente. Talvez algo assim:
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;
}
};
Então você provavelmente pode fazer isso:
typedef std::basic_string<char, std::char_traits<char>, my_allocator<char> > limited_string;
EDITAR: Acabei de fazer um teste para garantir que isso funcione conforme o esperado. O código a seguir testa.
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;
}
Aquele último s += s
vai colocá -lo por cima e causar um std::bad_alloc
exceção, (como meu limite está a menos de 64k). Infelizmente, GCC's std::basic_string::max_size()
A implementação não baseia seu resultado no alocador que você usa, por isso ainda afirma poder alocar mais. (Não tenho certeza se isso é um bug ou não ...).
Mas isso definitivamente permitirá que você impor limites rígidos aos tamanhos das cordas de uma maneira simples. Você pode até fazer com que o tamanho máximo seja um parâmetro de modelo, portanto, você só precisa escrever o código para o alocador uma vez.
Outras dicas
Eu concordo com Evan Teran sobre sua solução. Esta é apenas uma modificação de sua solução não mais:
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)));
}
};
Esteja ciente de que você não deve usar o polimorfismo com myalloc
. Então isso é desastroso:
// std::allocator doesn't have a virtual destructor
std::allocator<char>* alloc = new myalloc<char>;
Você apenas usa como se fosse um tipo separado, é seguro no seguinte caso:
myalloc<char, 1024> alloc; // max size == 1024
Você não pode criar uma classe com std :: string como pai e substituir c_str ()? Ou definir seu próprio c_str16 (), c_str32 (), etc e implementar a tradução lá?