2

I would like to ask some question. I am taking OOP C++ course in my faculty. We had some exercise connected with reinitializing dynamicly allocated char array. The problem is as follows:

 class state
{
    private:
    char *szstate; // a name of object


    public:
    state &operator = (const state&);
};

state & state::operator = (const state &cop)
{
    if (this != &cop)
    {
        szstate = new char [strlen(cop.szstate)+strlen("==.")+1];
        strcpy(this->szstate,"==.");
        strcat(this->szstate, cop.szstate);

        return *this;
    }
    else return *this;

}

In shortcut, creating an object "a" with the name "DEFAULT" and after b = a, should give me object b with name "==.DEFAULT". This will need more memory, so I used operator new for safety. The thing is that teacher said there should be also

delete []szstate 

before I reinitialize the "name" of object - I tried this, but segmentation fault appeared. Anyway, I found some writing in Stephen Prata's book, where he wrote that char* shall remain untouched; char* shall be treated as unchangeable constant, so that will be some kind of explanation for the error. So, am I doing something wrong or the teacher have no right? Thanks for the answers :)

juniorro
  • 47
  • 3
  • 1
    You *do* have a default constructor that initializes the pointer to `0`? – Some programmer dude Apr 16 '14 at 11:56
  • since in C++ why don't you use std::string instead of char*? This will redeem you from allocating and deallocating memory (i.e, it's more safe). – 101010 Apr 16 '14 at 11:58
  • There seems to be no code that frees the memory `szstate` points to before the call of `new char[..]`; this seems incorrect. – Codor Apr 16 '14 at 11:58
  • Don't forget, too, that deleting the pointer _before_ doing the `new` will result in undefined behavior if the `new` fails. – James Kanze Apr 16 '14 at 11:59
  • 1
    Since `szstate` is not default-initialized to `0`, `NULL` or `nullptr`, it contains a random value and `delete`-ing it will cause **UB**. – Chnossos Apr 16 '14 at 12:00
  • Your teacher didn't explain the rule of three to you? – James Kanze Apr 16 '14 at 12:02
  • You might want to consider reading about the copy-and-swap idiom: http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom – yati sagade Apr 16 '14 at 12:02
  • Joachim, I have an overloaded constructor only: state::state(char *szstate_name) { szstate = new char [strlen(szstate_name)+1]; strcpy(szstate, szstate_name); cout << "A state: " << szstate << " has been created." << endl; } and the prototype in class looks like: state(char* szstate = "DEFAULT"); 40two, I can't use std::string since it is forbidden at the course. – juniorro Apr 16 '14 at 12:06
  • @juniorro And what are all these `sz...` headers to the names. They create more than a little confusion. – James Kanze Apr 16 '14 at 12:17

2 Answers2

3

What's the initial value of szstate?

You should read up about the rule of three. Basically, if you need to hand write any of the three functions copy constructor, assignment and destructor, you probably have to write all of them. And have some non-trivial constructors as well. In your case:

  • You clearly need constructors, to ensure that szstate is initialized to a defined value, always. (Depending on the logic, a null pointer may be OK.)

  • You also need a destructor, to ensure that any dynamically allocated memory pointed to by szstate is correctly freed.

  • In the assignment operator, you must ensure that you never leave the object in an invalid state if there is an exception.

The classical way of doing this would be:

class State
{
    char* myState;
public:
    State()
        : myState( new char[1] )
    {
        myState[0] = '\0';
    }

    State( State const& other )
        : myState( new char[ strlen( other.myState ) + 1 ] )
    {
        strcpy( myState, other.myState );
    }

    ~State()
    {
        delete[] myState;
    }

    State& operator=( State const& other )
    {
        char const* tmp = new char[ strlen( other.myState) + 1 ];
        strcpy( tmp, other.myState );
        delete myState;
        myState = tmp;
        return *this;
    }
};

This implements pure deep copy semantics, but it should give you an indication of what you need to do.

Also: there's no need to check for self assignment in the assignment operator. The need for such a check is often a sign that you're doing something wrong, which won't be safe if there are exceptions.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Why is tmp char const? – Neil Kirk Apr 16 '14 at 12:24
  • I had all of them very similiar. Using the line "myState = tmp" helped me a lot. It works now. This is what i needed. Thank you! :) – juniorro Apr 16 '14 at 12:34
  • @NeilKirk Good question. Probably by habit; I can't recall the last time I used a `char*`. – James Kanze Apr 16 '14 at 13:14
  • Is it really good idea to always allocate memory? Will it always be optimal solution when used with stl collections? – marcinj Apr 16 '14 at 13:32
  • @marcin_j The above code is a simple example to show what you have to pay attention to. In practice, obviously, I'd use `std::string` here, and not do any dynamic allocation myself. And if I were implementing something like a string class, I would certainly look to use as few allocations as possible. – James Kanze Apr 16 '14 at 18:12
1

You should add constructor to state class and initialize in it szstate to nullptr (or 0), then in your operator= before new char call

delete[] szstate; // edit, no need to check szstate if its null (standard allows it)

this way you will not get segmentation fault

char* shall remain untouched; char* shall be treated as unchangeable constant, so that will be some kind of explanation for the error.

Maybe you mean string literals (const char[])? You should not delete those, but you have dynamic array which should be deleted once its no longer needed, or you you are about to loose pointer to it by assigning newly allocated array to it.

marcinj
  • 48,511
  • 9
  • 79
  • 100
  • 2
    The check is not needed, it's valid to `delete` (or `delete[]`) a null pointer. – Some programmer dude Apr 16 '14 at 12:00
  • You'll still have undefined behavior if the `new` fails. (And you don't have to check for a null pointer; `delete` of a null pointer is strictly defined as a no-op.) – James Kanze Apr 16 '14 at 12:00
  • If `new` fails and it is not called with `(std::nothrow)`, an exception will be thrown instead. – Chnossos Apr 16 '14 at 12:01
  • @Chnossos Which will result in undefined behavior, since the destructor of the object will be called with an invalid pointer. – James Kanze Apr 16 '14 at 12:02
  • @JamesKanze Not catching the exception will terminate the program anyway. But since marcin_j advised initializing the pointer in constructor, even if `new` fails the pointer will remain zero-initialize and thus the check is redundant. – Chnossos Apr 16 '14 at 12:06
  • removed check for null pointer value, this code still has other undefined behaviours, like it does not obey rule of three. – marcinj Apr 16 '14 at 12:06
  • @Chnossos Since there's undefined behavior, you can't be sure that the program will be terminated. – James Kanze Apr 16 '14 at 13:13