Search code examples
c++arraysstringdynamic-allocation

C++ - overloading operator >> for my string class


I realized string class MyString. Here is code:

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;

class MyString{
    private:
    char * content;
    int length;
    void copy(const MyString & source);
    public:
    MyString();
    MyString(const char * source);
    ~MyString();
    MyString(const MyString & source);
    void print(void);
    MyString & operator = (const MyString &source);
    friend std::ostream & operator << (std::ostream & out, const MyString&   towrite);
    friend std::istream & operator >> (std::istream & in, MyString & toread);
};

MyString::MyString(){
    content = new char[1];
    content[0] = '\0';
    length = 0;
}


MyString::MyString(const char *source){
    length = strlen(source);
    content = new char[length + 1];
    strcpy(content, source);
}

MyString::~MyString(){
    delete[] content;
}

void MyString::copy(const MyString & source){
    length = source.length;
    content = new char[length + 1];
    strcpy(content, source.content);
}

MyString::MyString(const MyString & source){
    copy(source);
}

void MyString::print(void){
    cout << "" << content << endl;
}

MyString &MyString::operator=(const MyString &source){
    copy(source);
    return *this;
}

std::ostream & operator<<(std::ostream & out,const MyString& towrite){
    out << towrite.content;
    return out;
}

std::istream & operator >> (std::istream & in, MyString & toread){
    int length;
    std::cout << "Enter length of word: " << endl;
    std::cin >> length;
    toread.length = length;
    toread.content = new char[toread.length+1];
    for (int i = 0; i < toread.length; i++){
    in >> toread.content[i] ;
    }
    toread.content[toread.length] = '\0';
    return in;
 }

My question is related to overloaded operator >>.

For this main program:

int main(){
    MyString word;
    std::cout<<"Enter some word: "<<endl;
    std::cin>>word;
    std::cout<<"Your entered: "<<word<<endl;
}

this is output:

Enter some word:

Enter length of word:

5

stack

Your entered: stack

Process returned 0 (0x0)   execution time : 8.313 s

Press any key to continue.

It prints correctly string user entered, but it doesn't "mimic" original string class on the way I want. Here is why.

In case of using C++ string class:

int main(){
    std::string word;
    std::cout<<"Enter some word: "<<endl;
    std::cin>>word;
    std::cout<<"Your entered: "<<word<<endl;
}

user doesn't need to enter length of word. Can I achieve this with my class?

EDIT1:

I did it on this way:

std::istream & operator >> (std::istream & in, MyString & toread){
    char *temp;
    temp = new char[100];
    char c;
    int i = 0;
    while(c != '\n'){
    c = getchar();
    temp[i++] = c;
    }
    temp[i] = '\0';
    int length = i-1;
    toread.length = length;
    toread.content = new char[toread.length+1];
    for(int i = 0 ; i < toread.length ; i++){
    toread.content[i] = temp[i];
    }
    delete [] temp;
    toread.content[toread.length+1]='\0';
}

It works as it should. However, I get warning because I didn't return "in":

||=== Build: Debug in fdsfsdf (compiler: GNU GCC Compiler) ===| C:\Users\hae\Desktop\fdsfsdf\main.cpp||In function 'std::istream& operator>>(std::istream&, MyString&)':| C:\Users\hae\Desktop\fdsfsdf\main.cpp|137|warning: no return statement in function returning non-void [-Wreturn-type]| ||=== Build finished: 0 error(s), 1 warning(s) (0 minute(s), 4 second(s)) ===| ||=== Run: Debug in fdsfsdf (compiler: GNU GCC Compiler) ===|


Solution

  • Here's a stripped down version of a similar class I wrote a long time ago. It's an antique, but it should work, and solves some of the issues with your class.

    class charray {
    public:
        charray();
        ~charray();
    
        charray(const charray&);
        charray(const char*);
    
        charray& operator=(const charray&);
        charray& operator=(const char*);
    
        void swap(charray&);
    
        const char* c_str() const
        { return m_elem; }
    
        unsigned int size() const
        { return m_size; }
    
    private:
        void m_resize(unsigned int size);
    
        char* m_elem;
        unsigned int m_size;
    };
    
    // private.
    void charray::m_resize(unsigned int size)
    {
        char* elem = new char[size+1];
    
        memcpy(elem, m_elem, std::min(m_size, size));
        elem[size] = '\0';
        delete [] m_elem;
    
        m_elem = elem;
        m_size = size;
    }
    
    // public.
    charray::charray()
        : m_elem(0), m_size(0)
    {
        m_resize(0);
    }
    
    charray::~charray()
    {
        delete [] m_elem;
    }
    
    charray::charray(const charray& src)
        : m_elem(0), m_size(0)
    {
        unsigned int size = src.size();
        m_resize(size);
        memcpy(m_elem, src.c_str(), size);
    }
    
    charray::charray(const char* src)
        : m_elem(0), m_size(0)
    {
        unsigned int size = std::strlen(src);
        m_resize(size);
        memcpy(m_elem, src, size);
    }
    
    charray& charray::operator=(const charray& rhs)
    {
        charray temp(rhs);
        this->swap(temp);
        return *this;
    }
    
    charray& charray::operator=(const char* rhs)
    {
        charray temp(rhs);
        this->swap(temp);
        return *this;
    }
    
    void charray::swap(charray& b)
    { 
        std::swap(m_elem, b.m_elem);
        std::swap(m_size, b.m_size);
    }
    

    Here is what you're probably most interested in. Pay close attention to the details. When dealing with memory directly, the difference between a working implementation and a broken one is often very subtle.

    Note: The operators are not friends. They do not access private data.

    std::ostream& operator<<(std::ostream& out, const charray& in)
    {
        return out << in.c_str();
    }
    
    std::istream& operator>>(std::istream& in, charray& out)
    {
        // verify no errors are set, flush tied streams, strip leading
        // whitespace.
        std::istream::sentry sentry(in);
        if (!sentry)
            return in;
    
        unsigned int size = 0;
        unsigned int tail = 0;
        char* temp = 0;
        int next; // @note int not char (to record EOF).
    
        while ((next = in.get()) != in.eof() && !std::isspace(next)) {
            // if temp buffer is exhausted, then double the buffer size.
            // (base size of 16).
            if (tail == size) {
                unsigned int newsize = std::max(2*size, 16u);
                char* newtemp = new char[newsize+1];
                memcpy(newtemp, temp, size);
                delete [] temp;
                temp = newtemp;
                size = newsize;
            }
            temp[tail++] = next;
        }
        // @note because the stream is prepeared with istream::sentry, there
        // will be at least one non-whitespace character in the stream.
        assert(temp != 0);
        temp[tail] = '\0';
        out = temp;
        delete [] temp;
        return in;
    }
    

    A much easier and safer way to do the exact same thing,

    #include <vector>
    std::istream& operator>>(std::istream& in, charray& out)
    {
        std::istream::sentry sentry(in);
        if (!sentry)
            return in;
    
        std::vector<char> temp;
        int next;
    
        while ((next = in.get()) != in.eof() && !std::isspace(next))
            temp.push_back(next);
        temp.push_back('\0');
        out = &temp[0];
        return in;
    }
    

    Edit
    The above is outmoded (pre C++11). A modern implementation would likely handle construction and assignment differently. Here are updated versions of those methods,

    Note: The method m_resize is gone. Everything is handled through constructors.

    charray::charray(const char* src, unsigned int size)
        : m_elem{ new char[size+1]{} }, m_size{ size }
    {
        std::copy(src, src + size, m_elem);
    }
    
    charray::charray()
        : charray(nullptr, 0)
    {}
    
    charray::charray(const charray& src)
        : charray(src.m_elem, src.m_size)
    {}
    
    charray::charray(const char* src)
        : charray(src, std::strlen(src))
    {}
    
    charray::charray(charray&& src)
        : m_elem{ src.m_elem }, m_size{ src.m_size }
    {
        src.m_elem = nullptr;
        src.m_size = 0;
    }
    
    // handle both move and copy assignment.
    charray& charray::operator=(charray rhs)
    {
        this->swap(rhs);
        return *this;
    }
    

    Hope this helps. Good luck.