Self-made string class Since I've been working with C strings these days, I decided to make a string class from the ground-up just to see how it would go. I wrote all labels in Spanish (my mother language) just for the sake of it. If you have problems understanding it, I'll simply translate them into English

(DISCLOSURE: I'm not thinking about actually using this in production code. I was just trying something new. I think the native std::string class is much better for these things. But if you can tell me how this would perform in production code, it would be of great help!).

My main goal was to made a class that could be edited and used with legacy code that needs a const char* instead of std::string.

What do you think about it? I have some doubts about my implementation: for instance, I don't know if returning the buffer as const char* is safe, or if using static_cast is necessary.

Here is my header file (Cadena.h):

#pragma once
#include &lt;iostream&gt;

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 &lt; 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&lt;const char*&gt;(bufer);
        }

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

        char&amp; operator [] (size_t indice) {
            if (indice &lt; 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&amp; operator &lt;&lt; (std::ostream&amp; salida, const Cadena&amp; cadena) {
            for (size_t longitud = 0; cadena.formaConstante()[longitud] != '\0'; ++longitud) {
                salida &lt;&lt; cadena.formaConstante()[longitud];
            }
            return salida;
        }

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

}
Possible undefined behaviour

There are several places that will lead to undefined behaviour.

In ajustar it can be possible that longitud &lt; taminio. We will then happily copy into cadenaTemporal[i] where longitud &lt; i &lt; taminio. It's easy to create an example for this:

David::Cadena example("oh oh");
std::cin &gt;&gt; example;


So instead of taminio use both longitud as well as taminio as upper limit.

In operator [] (size_t indice), you return bufer[-1] on example[0] if the string hasn't been initialized. This should either get documented or checked.

Consistency

borrarBufer() should also set tamino to zero. That seems to be your "empty string" scenario.

Don't include the \0 in len()

If the string is empty, len() should return 0, not -1. strlen() or std::string::size() don't return the \0 either.

    const size_t len() const {
        return tamanio;
    }


Use common interface

operator= should return a Cadena&amp;, not void. Similar, operator&gt;&gt; should return istream&amp;, not void.

Follow the rule of five

You provide a custom constructor. Therefore, you should also provide


a custom copy constructor,
a custom move constructor,
a custom copy assignment,
a custom move assignment and
a custom destructor.


You don't provide any of them, but you should. For example, I cannot copy a Cadena at the moment. I have to write

Cadena my_copy(other.formaConstante())


Use copy and swap idiom for operator=

As soon as you have a copy constructor, you can write operator= very easy:

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

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


Provide push(char) to add single characters

This will make operator&gt;&gt; a lot simpler.

Use a capacity

Your operator&gt;&gt; is \$\mathcal O(n^2)\$ since you have to realloc for every single char. Instead, allocate more memory than you actually need. Or use std::vector for your internal memory instead, it will take care of that automatically.