Я хотел бы иметь класс строки фиксированного размера. В идеале интерфейс должен соответствовать std::string
с той лишь разницей, что новый класс никогда не выделяет новую память. Предполагается, что это удобный класс для приложений, где следует избегать выделения новой памяти. Размер может быть статическим (известен во время компиляции).
Я думаю, что есть два пути. Первым было бы реализовать класс вокруг char
массив, а затем реализовать более или менее все функции, которые std::string
есть. Я также должен был бы реализовать некоторые операторы для создания std::string
s с заданной строкой фиксированного размера и т. д.
Второй метод, я даже не уверен, что это возможно, будет наследовать от std::string and
переопределить все функции, которые могут изменить размер строки. Я посмотрел в basic_string
заголовок в Visual Studio, и он не кажется виртуальным, поэтому я думаю, что это не тот путь.
Что бы вы назвали лучшим подходом для реализации такого класса?
Первым было бы реализовать класс вокруг
char
массив, а затем реализовать более или менее все функции, которыеstd::string
есть.
Это определенно путь. Это легко написать, легко использовать и трудно злоупотреблять.
template <size_t N>
class fixed_string {
char array[N+1];
size_t size;
public:
fixed_string() : size(0) { array[0] = '\0'; }
// all the special members can be defaulted
fixed_string(fixed_string const&) = default;
fixed_string(fixed_string&&) = default;
fixed_string& operator=(fixed_string const&) = default;
fixed_string& operator=(fixed_string&&) = default;
~fixed_string() = default;
// ...
};
Все аксессоры (data
, c_str
, begin
, end
, at
, operator[]
) являются однострочниками. Все алгоритмы поиска просты.
Единственный реальный вопрос дизайна — что вы хотите, чтобы мутации делали при неудаче. То есть:
fixed_string<5> foo("abcde");
foo += 'f'; // assert? throw? range-check internally and ignore?
// just not even add this and instead write a
// try_append() that returns optional<fixed_string&>?
У выбора дизайна есть свои преимущества и недостатки, но независимо от того, какой из них вы выберете, реализация каждой функции также будет очень краткой.
Второй метод, я даже не уверен, что это возможно, будет наследовать от
std::string
и переопределить все функции, которые могут изменить размер строки. Я посмотрел вbasic_string
заголовок в Visual Studio, и он не кажется виртуальным, поэтому я думаю, что это не тот путь.
Независимо от того, std::string
является virtual
не имеет отношения к вопросу о том, является ли это хорошей идеей. Вы определенно хотели бы начать с:
template <size_t N>
class fixed_string : private std::string { ... }
// ^^^^^^^^^
Поскольку ваш тип определенно не соответствует отношениям с std::string
, Это не std::string
это было бы просто реализовано с точки зрения этого. Частное наследование сделает этот код плохо сформированным:
std::string* p = new fixed_string<5>();
так что вам не нужно беспокоиться об отсутствии virtual
,
Тем не менее, наследуя от string
это приведет к гораздо более сложной и менее эффективной реализации, чем просто прямой путь, с большим количеством потенциальных ловушек. Это, вероятно возможный реализовать такую вещь, но я не понимаю, как это было бы хорошей идеей.
Я пошел вперед и сделал простой урок, который можно построить из. Структура выглядит следующим образом: базовый класс является интерфейсом только для объявлений, который будет содержать только сигнатуры типов конструкторов, которые вы хотите иметь, и список всех функций, которые должны быть реализованы в унаследованных классах — классах, поскольку они чисто виртуальный. Производный класс — это шаблонный класс, который имеет фактические реализации. Вы не используете класс напрямую, поскольку существует шаблон вспомогательной функции, который принимает тип, который вы хотите передать для каждого типа конструктора, который вы хотите поддерживать, и он возвращает этот тип.
#ifndef FIXED_STRING_H
#define FIXED_STRING_H
#include <string>
// This base class does not contain any member variables
// and no implementations of any constructor or function
// it serves as a definition to your interface as well as
// defining what methods must be implemented.
class fixed_string_base {
protected:
// The types of constructors you want to implement
template<size_t fixed_size>
explicit fixed_string_base( const char(&words)[fixed_size] ) {};
// The types of things you want to leave to default
fixed_string_base() = default;
fixed_string_base( fixed_string_base const& ) = default;
fixed_string_base( fixed_string_base&& ) = default;
fixed_string_base& operator=( fixed_string_base const& ) = default;
fixed_string_base& operator=( fixed_string_base&& ) = default;
virtual ~fixed_string_base() = default;
public:
// Put all of your pure virtual methods here that fixed_string must implement;
virtual char* c_str() = 0;
virtual size_t size() const = 0;
virtual size_t count() const = 0;
};
// This is the actual class that inherits from its non
// templated declaration interface that has the implementation of the needed constructor(s)
// and functions or methods that were declared purely virtual in the base class
template<size_t fixed_size>
class fixed_string_t : public fixed_string_base {
private:
size_t fixed_string_size_t = fixed_size;
char fixed_string_[fixed_size];
public:
//template<size_t fixed_size>
explicit fixed_string_t( const char(&words)[fixed_size] ) {
strncpy_s( fixed_string_, sizeof(char) * (fixed_size), &words[0], fixed_string_size_t );
fixed_string_[fixed_size] = '\0';
}
// c_str returns the character array.
virtual char* c_str() { return fixed_string_; }
// size gives the total size including the null terminator
virtual size_t size() const { return fixed_string_size_t; }
// count gives the size of the actual string without the null terminator
virtual size_t count() const { return fixed_string_size_t - 1; }
// Defaulted Constructors and Operators
fixed_string_t( fixed_string_t const& ) = default;
fixed_string_t( fixed_string_t&& ) = default;
fixed_string_t& operator=( fixed_string_t const& ) = default;
fixed_string_t& operator=( fixed_string_t&& ) = default;
virtual ~fixed_string_t() = default;
};
// Helper - Wrapper Function used to create the templated type
template<size_t fixed_size>
fixed_string_t<fixed_size> fixed_string( const char(&words)[fixed_size] ) {
return fixed_string_t<fixed_size>( words );
}
#endif // FIXED_STRING_H
Чтобы использовать это будет выглядеть примерно так:
#include <iostream>
#include "FixedString.h"
int main() {
auto c = fixed_string( "hello" );
std::cout << c.c_str() << " has a size of " c.size() << " with\n"<< "a character count of " << c.count() << std::endl;
return 0;
}
Единственная вещь с этим в том виде, в каком она существует в настоящее время, состоит в том, что этот объект не может быть изменен. Сама строка исправлена. Это просто шаблон или демонстрация того, каким может быть шаблон проектирования того типа, который вы ищете. Вы можете добавить к этому или расширить его, позаимствовать у него или даже полностью игнорировать его. Выбор за вами.