0

I have implemented a class string, similar to std::string one. I have a problem when the destructor is called: the field length has the length of the characters allocated in field. This is the class:

class indexException:public std::exception
{
    public:
    virtual const char* what()
    {
        return "Index is either too long, or negative";
    }
};

class string
{
    public:
    static const unsigned int length_max=100;
    string(const char* field=NULL)
    {
        if(field!=NULL)
        {
            const unsigned int length=strlen(field);
            this->field=new char[length+1];
            this->length=length;
            for(unsigned int i=0;i<=length;i++)
                this->field[i]=field[i];
        }
        else
        {
            this->field=NULL;
            length=0;
        }
    }
    string(string& str)
    {
        string(str.field);
    }
    ~string()
    {
        if(length>0)
            delete field;
    }
    char& operator[] (int i) const throw()
    {
        try
        {
            if(i<0 || i>=(int)length)
                throw indexException();
        }
        catch(indexException& e)
        {
            std::cerr << e.what() << std::endl;
        }
        return field[i];
    }
    string& operator=(const char* field)
    {
        const unsigned int length=strlen(field);
        if(this->length>0)
            delete this->field;
        this->field=new char[length];
        this->length=length;
        for(unsigned int i=0;i<length;i++)
            this->field[i]=field[i];
        return *this;
    }
    string& operator= (const string& str)
    {
        if(this!=&str)
            *this=str.field;
        return *this;
    }
    operator char* ()
    {
        return field;
    }
    friend std::ostream& operator<< (std::ostream& out, string& str);
    friend std::istream& operator>> (std::istream& in, string& str);
    public:
    unsigned int length;
    char* field;
};

std::ostream& operator<<(std::ostream& out, string& str)
{
    out << str.field;
    return out;
}

std::istream& operator>> (std::istream& in, string& str)
{
    char temp[string::length_max];
    in >> temp;
    str=temp;
    return in;
}

If I use the assignment operator, this doesn't cause a segmentation fault. But it undirectly cause it. I explain how:

int main(int argc,char** argv)
{
    string str="hi";
    string str2=str;
    return 0;
}

Putting a breakpoint into the assignment operator overloading, I realized that the assigment operator doesn't cause segmentation fault. The problem is after, when exiting from main. If I remove the destructor I don't get this segmentation fault, but I would know why I get this problem.

Edit: I have understood where's the problem. I followed your suggestions but it still goes to segmentation fault. But now it doesn't crash anymore on the destructor method, but on the assignment operator overloading:

    string& operator=(const char* field)
    {
        unsigned int length=0;
        if(field!=NULL)
            length=strlen(field);
        else
            field="";
        if(this->length>0)
            delete[] this->field;
        this->field=new char[length+1];
        this->length=length;
        strcpy(this->field,field);
        return *this;
    }

The problem is when I delete this->field, the debugger stops there. An example of segmentation fault:

string str("hi");
string str2=str;

This causes segmentation fault.I suppone it's because str2 is not initialized, and length has an undefined value. If I instead do this:

string str("hi");
string str2;
str2=str;

There isn't any segmentation fault.Why? I thought that calling :

string str2;

Was also calling the constructor, or is that the "=" operator has the precedence? How to solve this?

PS: I also changed other things,like the copy constructor. Full code is here: http://pastebin.com/ubRgaVr8

Solved: I changed the copy constructor as suggested in the accepted reply:

    string(const string& str)
    {
        length=str.length;
        field=new char[str.length+1];
        memcpy(field,str.field,length+1);
    }
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • 2
    Your copy constructor doesn't work like that. It should be `string(string const & rhs) : string(rhs.field) { }`, but that's new in C++11 and not very widely supported yet. – Kerrek SB Feb 18 '12 at 00:49
  • 3
    If you're allocating with new[] you want to delete with delete[]. – Retired Ninja Feb 18 '12 at 00:49
  • I tried using delete[] instead of delete, also tried to change the if condition as Vyktor said.It still leads to segmentation fault. – Ramy Al Zuhouri Feb 18 '12 at 00:57
  • Are you sure you set the last character of `field` to 0? I do see you use `strlen()` on the `field` parameter, which on copy is the `field` member of another object. – wilhelmtell Feb 18 '12 at 03:12
  • I changed it to memcpy(this->field,field,length+1); so now it copies also the terminating character, but I still have that problem – Ramy Al Zuhouri Feb 18 '12 at 14:01

4 Answers4

3

Your copy constructor doesn't initialise the object.

string(string& str)
{
    string(str.field); // Does nothing
}

string(str.field)creates an unnamed stringand immediately throws it away. It does not initialise this object using a different constructor.

Since your object now consists only of randomness, bad things will happen when you try to destroy it.

To make sure things are initialised, make a private member function

void initializeFromChars(const char* cString);

that does the work and use it in your constructors and assignment operator.

molbdnilo
  • 64,751
  • 3
  • 43
  • 82
2

Once you allocate memory with

field = new char[length+1];

You should delete it with:

 delete [] field;

And you're not checking whether your allocation was successful.

Another thing considered good practice is setting field to NULL after delete so it won't get deleted twice (if you start delivering classes) for example:

~string(){
    delete [] field;
    // field = NULL;
}

Note: according to Dietmar Kühl setting field=NULL isn't good practice (take a look at the comments) and choose your way, here's question specifically about this: Is it worth setting pointers to NULL in a destructor? .

Note 2: KerrekSB pointed out that delete [] field will do nothing if pointer is NULL and whole condition is unnecessary.

Than in string& operator=(const char* field) you probably want to allocate length + 1 and iterate to it too (to include terminating NULL).

And I don't like your string& operator= (const string& str), you have cached info on length of string and you're using strlen() and than manual copy char by char.

Your copy constructor also looks bad... You should "copy" manual allocation and copy byte by byte to it. Or rather build protected function like fromCString(const char *) and use it in both constructors and assign operators.

If those doesn't help ask in comment for more help.

Community
  • 1
  • 1
Vyktor
  • 20,559
  • 6
  • 64
  • 96
  • 1
    There's no need to set `field` to NULL. It's deleted, gone, dead (THIS, IS AN EX-PARROT!). You'll never see this field again once the destructor is done with its business. – wilhelmtell Feb 18 '12 at 00:59
  • @wilhelmtell I'm sorry but what's an *EX-PARROT*? And my reference is: http://www.amazon.com/Teach-Yourself-21-Days/dp/0672310708 (but earlier volume). – Vyktor Feb 18 '12 at 01:03
  • @wilhelmtell anyway it's considered good practice because when you start delivering classes you may accidentally delete the same data twice or when you have many attributes you may try to delete the same twice. I'd like some good reference to confirm that's not considered good practice. – Vyktor Feb 18 '12 at 01:12
  • Please, honestly and in full presence of the senses, can you tell me the purpose of the `field != NULL` check? – Kerrek SB Feb 18 '12 at 01:33
  • @Vyktor: I think the ex-parrot is a reference to Monty Python [sketch](http://www.mtholyoke.edu/~ebarnes/python/dead-parrot.htm). Setting pointers to null in a destructor is pointless and creates a false sense of safety: the same pointer can be used elsewhere and this copy won't be set to null. I don't many C++ experts consider this good practice and generally I'd rather listen to the experts for advice than the newbies. – Dietmar Kühl Feb 18 '12 at 01:46
  • @KerrekSB I'd check only `field != NULL` not the length (for the case of failed allocation which he isn't checking) or for case when he implements more functions and one of them would clear text and forget to clean length... + as soon as he will start delivering classes (such as `stringUTF8` or so) he may accidentally delete in earlier destructor. – Vyktor Feb 18 '12 at 12:23
  • @Vyktor: I think you got it the wrong way round. The NULL check does *absolutely nothing*, and the program behaves exactly the same if you omitted the check altogether. – Kerrek SB Feb 18 '12 at 12:29
  • @DietmarKühl I agree that it's pointless in this class, but when you're delivering classes, building more complex structures which may or may not have data allocated is the only way of knowing that "yes there's something to be cleared/deleted" (and when you're not putting it every destructor, you'll just forget). I provided my reference which made me thing that it's good practice and I'm asking you to provide me yours (in the way "please show me"). I don't know any of you guys in person but your "about me" made me changed my answer :) – Vyktor Feb 18 '12 at 12:33
  • @KerrekSB if this `this->field=new char[length+1];` fails or when `length = 0` than `field == NULL`, no? – Vyktor Feb 18 '12 at 12:35
  • @Vyktor: When `new` fails, there's an exception. When `length == 0`, you still say `new char[1]`. None of this matters, though, because my only contention is that you don't understand how `delete[]` works, yet you are proposing critical code that uses it. – Kerrek SB Feb 18 '12 at 12:36
  • @Vyktor: Any decent C++ book (look in our FAQ for a nice list) should tell you that `delete[]` will do nothing if the argument is a null pointer. There are many good books, though a straight up reference such as Bjarne Stroustrup's original might be the easiest way to simply "look up what a specific thing does". In this case you'd just go and look up what `delete[]` does, and conclude that `~string() { delete[] field; }` is all you need. By design, you know that `field` is either null or points to an allocated piece of memory, so this is always correct. – Kerrek SB Feb 18 '12 at 13:20
  • @KerrekSB thank you for you patience. I've updated my answer, is it better now? – Vyktor Feb 18 '12 at 13:27
  • @Vyktor: Yes, although one could argue that there's really no point in setting `field` to null, since you cannot possibly refer to it legally after the destructor completes. – Kerrek SB Feb 18 '12 at 13:30
  • @KerrekSB that's why added the first note (the one that Dietmar suggested). In this class it is unnecessary indeed. But as soon as you start delivering classes from it I believe you should do it. And I believe it's better for programmer to have routine to set pointer to null after deletion and not to think whether (not) to set it. However if you (as more experienced C++ programmer) tell me that I should remove it I'll do it (this discussion is getting long)... Or feel free to edit yourself. – Vyktor Feb 18 '12 at 13:37
  • @Vyktor: Finding references for **not** doing something is harder than quoting bad advice. See e.g. [Michael Burr's answer](http://stackoverflow.com/questions/3060006/is-it-worth-setting-pointers-to-null-in-a-destructor) to a question asking specifically about this. I have checked the [C++ FAQ](http://www.parashift.com/c++-faq-lite/), Scott Meyers "Effective C++", and Herb Sutter/Andrei Alexandrescu's "C++ Coding Standards": none recommends setting members to NULL - however, all of them know that the destructor is a last thing happing to an object. – Dietmar Kühl Feb 18 '12 at 14:28
  • @DietmarKühl thank you for patience and effort to trace documentation, I've added link to question to my answer and commented out setting field. Good to learn something new. – Vyktor Feb 18 '12 at 15:12
2

Your destructor uses delete, when it should use delete[].

wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
2

EDIT: Scrapped my previous answer, as it was incorrect.

The problem appears to be the copy constructor, you are passing the field from the source instance as though it is merely another null terminated char*, but it isn't.

You don't copy the null character at the end during the char* assignment invoked by the previous statement, you use an internal length field instead, and copy only that many bytes.

so your copy constructor should be:

string(string& str)
{
   length = str.length;
   field = new char[length];
   memcpy(field, str.field, length);
}

or, if you want to preserve compatibility with null terminated functions, and you have ensured that the null is kept for all other assignments/constructors, etc:

string(string& str)
{
   length = str.length;
   field = new char[length + 1];
   memcpy(field, str.field, length + 1);
}

In fact, the mixing null terminated, and specified length strings so much throughout your class appears to be confusing you.

I would create an internal, private, single disposal method, and an array of methods to set various source types, and have the constructors, assignment operators, and destructors use those instead.

That way you only have a single places where any given operation occurs, rather than juggling many minor variations on the same functionality. For example:

private:
    void internalSet(const char *source) {
        if (source == NULL) {
            length = 0;
            field = NULL;
        }else{
            length = strlen(source);
            field = new char[length];
            memcpy(field, source, length);
        }
    }

    void internalSet(const string &source) {
        length = source.length;
        if (length > 0) {
            field = new char[length];
            memcpy(field, source.field, length);
        }else{
            field = NULL;
        }
    }

    void internalDispose() {
        delete[] field;
    }

public:
    string() : field(NULL), length(0) {}

    string(const string& source) { internalSet(source); }
    string(const char *source) { internalSet(source); }

    ~string() { internalDispose(); }

    string& operator=(const char *source) {
        internalDispose();
        internalSet(source);
        return *this;
    }

    string& operator=(const string &source) {
        internalDispose();
        internalSet(source);
        return *this;
    }

    void clear() {
        internalDispose();
        length = 0;
    }
jka6510
  • 826
  • 5
  • 8
  • This is very poor style. You should really use initializer lists. – Kerrek SB Feb 18 '12 at 01:32
  • Poor style? What's style to do with it - its entirely subjective - next you'll be commenting on whether it should have newlines before braces. I try not to use constructs that are not already evident in the question, in case the OP has not yet encountered them - C++ is a large and complex language after all. – jka6510 Feb 18 '12 at 01:53
  • Without a no arg constructor, c++ will refuse to create objects without passing parameters. Adding a no arg constructor will provide no benefit in this case. Also, since the code isn't using pointers or references, none of the instances of string are allocated on the heap, they are all allocated on the stack. – MJD Feb 18 '12 at 07:09
  • I agree with @KerrekSB: this is a bad style, that easily leads to extra call to default constructor in case of complex data members. – lapk Feb 18 '12 at 07:12
  • Good point MJD - I wasn't looking closely enough - I didn't see the default value on the const char* constructor, which is obviously being used. And yes, it is stack I meant, not heap. – jka6510 Feb 18 '12 at 12:23
  • string(const char* field=NULL) I thought that the parameter was optional, because initialized to NULL, isn't that true? – Ramy Al Zuhouri Feb 18 '12 at 12:46
  • you're right, the copy costructor was just recycling the assignment operator.But in the assignment operator I was deleting field if length was non zero.But length was initialized, and this was causing the segmentation fault problem.Solved it. – Ramy Al Zuhouri Feb 18 '12 at 16:11