Implementing a Custom String Container in C++

String Container as Class Template

Although the string container is implemented as a class template, explicit template instantiation is not required when creating string objects due to implicit instantiation mechanisms.

String Class Instantiation

When working with string objects, it's essential to understand that you're manipulating contiguous storage space. This space contains metadata for data size, storage capacity, and the address of the first element.

Both const and non-const objects share identical constructor and destructor implementations since the initialization list is considered the point of object instantiation.

#pragma once
#include <cstring>
#include <iostream>

namespace custom {
class TextBuffer {
private:
    size_t data_size;
    size_t buffer_capacity;
    char* buffer_data;

public:
    // Constructor
    TextBuffer(const char* input_str = "")
        : data_size(std::strlen(input_str))
        , buffer_capacity(data_size)
        , buffer_data(new char[buffer_capacity + 1]) {
        std::memcpy(buffer_data, input_str, data_size + 1);
        buffer_data[data_size] = '\0';
    }

    // Destructor
    ~TextBuffer() {
        delete[] buffer_data;
        buffer_data = nullptr;
        data_size = buffer_capacity = 0;
    }
};
}

The expression std::memcpy(buffer_data, input_str, data_size + 1) uses data_size + 1 because std::strlen counts characters excluding the null terminator, making the actual data size one byte larger.

Data Access and Traversal

Const objects cannot use methods like push_back, append, or += for data insertion.

Data Insertion Methods

push_back

void append_char(char character) {
    if (data_size == buffer_capacity) {
        expand_buffer(buffer_capacity == 0 ? 4 : (buffer_capacity * 2));
    }
    buffer_data[data_size] = character;
    data_size++;
    buffer_data[data_size] = '\0';
}

The expand_buffer method increases the string's storage capacity when needed. Capacity reduction is generally avoided due to performance overhead, though some compilers may implement it after clear operations.

void expand_buffer(size_t new_capacity) {
    if (new_capacity > buffer_capacity) {
        char* new_buffer = new char[new_capacity + 1];
        std::memcpy(new_buffer, buffer_data, data_size + 1);
        delete[] buffer_data;
        buffer_data = new_buffer;
        buffer_capacity = new_capacity;
    }
}

append

void append_string(const char* source_str) {
    size_t source_length = std::strlen(source_str);
    if (data_size + source_length > buffer_capacity) {
        expand_buffer(data_size + source_length);
    }
    std::memcpy(buffer_data + data_size, source_str, source_length + 1);
    data_size += source_length;
}

+= Operator Overloads

TextBuffer& operator+=(char character) {
    append_char(character);
    return *this;
}

TextBuffer& operator+=(const char* source_str) {
    append_string(source_str);
    return *this;
}

TextBuffer& operator+=(const TextBuffer& other) {
    append_string(other.buffer_data);
    return *this;
}

Traversal Implementation

public:
    typedef char* iterator;
    typedef const char* const_iterator;

    char& operator[](size_t index) {
        if (index < data_size) {
            return buffer_data[index];
        }
        throw std::out_of_range("Index out of range");
    }

    const char& operator[](size_t index) const {
        if (index < data_size) {
            return buffer_data[index];
        }
        throw std::out_of_range("Index out of range");
    }

    iterator begin() { return buffer_data; }
    iterator end() { return buffer_data + data_size; }
    const_iterator begin() const { return buffer_data; }
    const_iterator end() const { return buffer_data + data_size; }

Insert and Erase Operations

Insert Implementation

TextBuffer& insert_chars(size_t position, size_t count, char character) {
    assert(position <= data_size);
    
    if (data_size + count > buffer_capacity) {
        expand_buffer(data_size + count);
    }
    
    for (size_t i = data_size; i >= position; i--) {
        buffer_data[i + count] = buffer_data[i];
    }
    
    for (size_t i = 0; i < count; i++) {
        buffer_data[position + i] = character;
    }
    data_size += count;
    
    return *this;
}

Erase Implementation

TextBuffer& remove_chars(size_t position, size_t count = npos) {
    assert(position <= data_size);
    
    if (count == npos || position + count >= data_size) {
        buffer_data[position] = '\0';
        data_size = position;
    } else {
        size_t remaining = data_size - (position + count);
        std::memmove(buffer_data + position, 
                    buffer_data + position + count, 
                    remaining);
        data_size -= count;
        buffer_data[data_size] = '\0';
    }
    return *this;
}

Find Operatiosn

size_t find_char(char target, size_t start_pos = 0) const {
    assert(start_pos <= data_size);
    
    for (size_t i = start_pos; i < data_size; i++) {
        if (buffer_data[i] == target) {
            return i;
        }
    }
    return npos;
}

size_t find_string(const char* target_str, size_t start_pos = 0) const {
    assert(start_pos <= data_size);
    
    const char* result = std::strstr(buffer_data + start_pos, target_str);
    return result ? result - buffer_data : npos;
}

Substring Extraction

char* extract_substring(size_t start_pos = 0, size_t length = npos) const {
    assert(start_pos <= data_size);
    
    if (length == npos || start_pos + length > data_size) {
        length = data_size - start_pos;
    }
    
    char* substring = new char[length + 1];
    std::memcpy(substring, buffer_data + start_pos, length);
    substring[length] = '\0';
    
    return substring;
}

Comparison Operators

bool operator<(const TextBuffer& other) const {
    int result = std::memcmp(buffer_data, other.buffer_data, 
                            std::min(data_size, other.data_size));
    return result == 0 ? data_size < other.data_size : result < 0;
}

bool operator==(const TextBuffer& other) const {
    return data_size == other.data_size && 
           std::memcmp(buffer_data, other.buffer_data, data_size) == 0;
}

bool operator<=(const TextBuffer& other) const {
    return *this == other || *this < other;
}

bool operator>(const TextBuffer& other) const {
    return !(*this <= other);
}

bool operator>=(const TextBuffer& other) const {
    return !(*this < other);
}

Stream Operators

void clear_data() {
    buffer_data[0] = '\0';
    data_size = 0;
}

std::ostream& operator<<(std::ostream& os, const TextBuffer& buffer) {
    for (size_t i = 0; i < buffer.data_size; i++) {
        os << buffer.buffer_data[i];
    }
    return os;
}

std::istream& operator>>(std::istream& is, TextBuffer& buffer) {
    buffer.clear_data();
    
    char temp_buffer[128];
    char ch;
    
    // Skip whitespace
    while (is.get(ch) && std::isspace(ch)) {}
    
    if (!is.eof()) {
        is.putback(ch);
    }
    
    size_t index = 0;
    while (is.get(ch) && !std::isspace(ch)) {
        temp_buffer[index++] = ch;
        if (index == 127) {
            temp_buffer[index] = '\0';
            buffer += temp_buffer;
            index = 0;
        }
    }
    
    if (index > 0) {
        temp_buffer[index] = '\0';
        buffer += temp_buffer;
    }
    
    return is;
}

Copy Control Operations

TextBuffer(const TextBuffer& other) {
    buffer_data = new char[other.buffer_capacity + 1];
    std::memcpy(buffer_data, other.buffer_data, other.data_size + 1);
    data_size = other.data_size;
    buffer_capacity = other.buffer_capacity;
}

TextBuffer& operator=(TextBuffer other) {
    swap_data(*this, other);
    return *this;
}

void swap_data(TextBuffer& first, TextBuffer& second) {
    std::swap(first.buffer_data, second.buffer_data);
    std::swap(first.data_size, second.data_size);
    std::swap(first.buffer_capacity, second.buffer_capacity);
}

Tags: C++ STL string container implementation

Posted on Wed, 30 Sep 2026 16:21:19 +0000 by Unholy Prayer