Самопальный класса String


Поскольку я работаю с C-строками в эти дни, я решил сделать класс String с нуля, просто чтобы посмотреть, как оно пойдет. Я писал все надписи на испанском языке (мой родной язык) просто ради этого. Если у вас есть проблемы с пониманием, я просто перевожу их на английский

(Раскрытие: я не думаю о фактически используя это в рабочем коде. Я просто пробую что-то новое. Я думаю, что родной std::string класс-это намного лучше для этих вещей. Но если вы можете сказать мне, как это будет работать в производственном коде, это будет большим подспорьем!).

Моей основной целью было сделано в классе, которые могут быть отредактированы и использованы с унаследованным кодом, который нуждается в const char* вместо std::string.

Что вы думаете об этом? У меня есть некоторые сомнения по поводу моей реализации: например, я не знаю, если возврат буфера const char* является безопасным, или при использовании static_cast необходимо.

Вот мой заголовочный файл (Кадена.ч):

#pragma once
#include <iostream>

namespace David {

    class Cadena {
    private:
        char* bufer;
        size_t tamanio;

        size_t obtenerLongitud(const char* cadena) {
            size_t longitud = 0;
            for (longitud = 0; cadena[longitud] != '\0'; ++longitud);
            ++longitud;
            return longitud;
        }

        void borrarBufer() {
            delete[] bufer;
            bufer = nullptr;
        }

        void ajustar(size_t longitud) {
            char* cadenaTemporal = new char[longitud + 1];
            if (bufer != nullptr) {
                for (size_t i = 0; i < tamanio; ++i) {
                    cadenaTemporal[i] = bufer[i];
                }
            }
            borrarBufer();
            bufer = cadenaTemporal;
            cadenaTemporal = nullptr;
            tamanio = longitud + 1;
        }

    public:
        Cadena(const char* cadena) {
            tamanio = obtenerLongitud(cadena);
            bufer = new char[tamanio];
            for (size_t longitud = 0; cadena[longitud] != '\0'; ++longitud) {
                bufer[longitud] = cadena[longitud];
            }
            bufer[tamanio - 1] = '\0';
        }

        Cadena() {
            tamanio = 0;
            bufer = nullptr;
        }

        ~Cadena() {
            borrarBufer();
        }

        const char* formaConstante() const {
            return static_cast<const char*>(bufer);
        }

        const size_t len() const {
            return tamanio - 1;
        }

        char& operator [] (size_t indice) {
            if (indice < tamanio)
                return bufer[indice];
            return bufer[tamanio - 1];
        }

        void operator = (const char* cadena) {
            borrarBufer();
            tamanio = obtenerLongitud(cadena);
            bufer = new char[tamanio];
            for (size_t longitud = 0; cadena[longitud] != '\0'; ++longitud) {
                bufer[longitud] = cadena[longitud];
            }
            bufer[tamanio - 1] = '\0';
        }

        friend std::ostream& operator << (std::ostream& salida, const Cadena& cadena) {
            for (size_t longitud = 0; cadena.formaConstante()[longitud] != '\0'; ++longitud) {
                salida << cadena.formaConstante()[longitud];
            }
            return salida;
        }

        friend void operator >> (std::istream& entrada, Cadena& cadena) {
            if(cadena.bufer != nullptr) cadena.borrarBufer();
            char letra = ' ';
            size_t longitud = 0;
            while (letra != '\n') {
                entrada >> std::noskipws >> letra;
                cadena.ajustar(longitud);
                cadena.bufer[longitud] = letra;
                ++longitud;
            }
            cadena.bufer[longitud - 1] = '\0';
        }
    };

}


Комментарии
3 ответа

Возможно неопределенное поведение

Есть несколько мест, что приведет к неопределенному поведению.

В ajustar Это может быть возможным, что longitud < taminio. Мы потом радостно скопировать в cadenaTemporal[i] где longitud < i < taminio. Это легко создать пример для этого:

David::Cadena example("oh oh");
std::cin >> example;

Поэтому вместо того, чтобы taminio использовать оба longitud а также taminio в качестве верхнего предела.

В operator [] (size_t indice)вы возвращаетесь bufer[-1] на example[0] если строка не инициализирована. Это должно либо получить документально или проверены.

Последовательность

borrarBufer() следует также установить tamino к нулю. Это, кажется, ваше "пустая строка" сценарий.

Не включать \0 в len()

Если строка пуста, len() должны вернуть 0не -1. strlen() или std::string::size() не вернуть \0 либо.

    const size_t len() const {
return tamanio;
}

Использовать общий интерфейс

operator= должны вернуть Cadena&не void. Похожие, operator>> должны вернуть istream&не void.

Следовать правилу пяти

Вы предоставляете пользовательский конструктор. Таким образом вы должны также обеспечить


  • пользовательский конструктор копирования,

  • пользовательский конструктор перемещения,

  • пользовательское задание копирования,

  • пользовательское назначение перемещения и

  • пользовательский деструктор.

Вы не предоставляете какие-либо из них, но вы должны. Например, я не могу скопировать Cadena на данный момент. Я должен написать

Cadena my_copy(other.formaConstante())

Использование копирования и swap идиома operator=

Как только у вас есть конструктор копирования, вы можете написать operator= очень легко:

    void swap(Cadena & other) {
std::swap(tamino, other.tamino);
std::swap(bufer, other.bufer);
}

Cadena& operator=(const char* cadena) {
Cadena temporary(cadena);
swap(cadena);
return *this;
}

Обеспечить push(char) для добавления одного символа

Это сделает operator>> намного проще.

Использовать емкость

Ваш operator>> это \$\mathcal o(п^2)\$, так как вы должны realloc для каждого чара. Вместо того чтобы выделить больше памяти, чем вам действительно нужно. Или использовать std::vector для внутренней памяти вместо этого, он будет заботиться об этом автоматически.

8
ответ дан 24 марта 2018 в 05:03 Источник Поделиться


  1. Не используя стандартные соглашения об именовании хуже в C++, чем в язык, не настолько мощный, шаблоны или другие либерально-используется отражение объектов. Это, в сущности, бары класс от взаимодействия с стандартной библиотеки.

  2. Делая 0-Терминатор часть строки-это интересный выбор. Как в проклятье "Чтоб ты жил в интересные времена".

  3. Обеспечьте стандартный итератор-интерфейс. Это позволяет использовать множество стандартных алгоритмов и по дальности-петля.

  4. Не бросайте, если вам не придется. Кастинг-это слишком обойти применение компилятора типа-безопасность, и это так опасно.
    В вашем случае конверсии char* для const char*неявное преобразование достаточно.

  5. В то время как это действует решение о возвращении Терминатора, если индекс находится вне диапазона, это может быть немного дорогостоящим для предельной полезности.

  6. Обеспечить const-интерфейс, кроме того неconst один. В противном случае, будучи константными-исправить будет довольно трудно на ваших потребителей.

  7. Потоковая передача каждого символа в строке сам по себе противопоказан, так как он означает, что все настройки и проверка должна быть выполнена для каждого из них сам по себе, а не для всех вместе. Кроме того, другой вывод можно чередовать.

  8. Вы не предоставили копировать/перемещать конструктор/назначение, хотя вы предоставляете dtor, чтобы освободить содержащихся ресурса. Ум верховенства 5/3/0.

  9. В то время как вы можете решить не предоставлять возможность, рассмотрите, по крайней мере, через один в op>> чтобы избежать квадратичного поведения.

6
ответ дан 24 марта 2018 в 07:03 Источник Поделиться

Комментарий Код

Прагм

Не все компиляторы поддерживают это ПРАГМА.

#pragma once

Размер Против Мощности

Имея только размер обрекает вас на перераспределение каждый раз новый буфер текст добавляется в строку. Он может быть более эффективным, чтобы выделить буфер немного больше, чем нужно (capacity) и только перераспределения, когда size (tamanio) превышает емкость.

        char* bufer;
size_t tamanio;

Размер включает в себя \0

Конечно.

        size_t obtenerLongitud(const char* cadena) {
size_t longitud = 0;
for (longitud = 0; cadena[longitud] != '\0'; ++longitud);
++longitud;
return longitud;
}

Но вы должны быть последовательны, чтобы удостовериться, что buffer[tamanio] == '\0' в остальной части кода.

Корректировка размера?

Вы не гарантируете:

buffer[tamanio] == '\0'

Этот новый

            char* cadenaTemporal = new char[longitud + 1];

Не нуль пространство прежде, чем возвратить это. Содержание довольно случайный.

Это совершенно пустая трата времени:

            cadenaTemporal = nullptr;

Конструктор

Вы не соблюдаете правило трех.

Bsically если определить один из деструктор/конструктор копирования/присваивания копии вы, вероятно, следует определить все три. В вашем случае вы не определите конструктор копирования. В результате вы получите двойное удаление, Если вы каждую копию вашей строке.

{
Cadena tmp1("Test");
Cadena tmp2(tmp1); // Compiler generated copy constructor
// Does a simple shallow copy. So both
// objects point at the same buffer.
}
// Destructor of both objects called here.
// The destructor of `tmp2` first which deletes the buffer,
// then the destructor of `tmp1` which destroys the same buffer.

Оператор[] против на()

В C++ у нас есть руководящий принцип. Вы не должны платить за то, что вы не используете:

        char& operator [] (size_t indice) {
if (indice < tamanio)
return bufer[indice];
return bufer[tamanio - 1];
}

В большинстве случаев indice уже проверено и гарантировано быть в диапазоне [0, tamanio]. Посмотрите на стандартный вариант использования.

  Cadena  tmp1("Test");
for(int loop = 0; loop < tmp1.len(); ++loop) {
std::cout << tmp1[loop];
}

Мы уже гарантировали, что индекс находится в правильном диапазоне. Поэтому каждый вызов operator[] есть проверить, что не нужен. Именно поэтому стандартный std::string есть два способа доступа к буферу. operator[] которая бесконтрольно и at() который проверяет индекс находится в диапазоне.

Вряд ли целесообразно вернуться общезначимое, когда индекс находится вне диапазона. Почему не бросить исключение, так что разработчик знает, что что-то пошло не так.

Назначение Копия

Уверен, что это работает (и отлично действует). Но обычно мы возвращаем ссылку на объект. Так что он может действовать как стандартного типа, так и использоваться в виде цепи выражение.

Вы должны также искать копии и идиома своп.

Семантика Перемещения

Начиная с C++11 (семь лет назад) язык имеет семантику перемещения. Ваш класс должен также поддерживать эту. Это позволяет для эффективного перемещения объекта. это требует определения двух операторов.

 Cadena(Cadena&& m) noexcept;           // Move constructor
Cadena& operator=(Cadena&&) noexcept; // Move assignement

Оператор ввода

Уверен, что это работает.
Я хотел бы отметить, что он имеет различную семантику в std::string которые подразумевают читает одно слово. Также помните мою записку о возможности и выше размер. Эта функция является очень неэффективным, так как она перераспределяет буфера после чтения каждого символа.

        friend void operator >> (std::istream& entrada, Cadena& cadena) {
if(cadena.bufer != nullptr) cadena.borrarBufer();
char letra = ' ';
size_t longitud = 0;
while (letra != '\n') {
entrada >> std::noskipws >> letra;
cadena.ajustar(longitud);
cadena.bufer[longitud] = letra;
++longitud;
}
cadena.bufer[longitud - 1] = '\0';
}

6
ответ дан 24 марта 2018 в 08:03 Источник Поделиться