• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 818
  • Last Modified:

const and member list initialisation

Suppose I have a very simple class

class One
{
public:
     One();
private:
     const int a;
     char* m;<---------------(1)
};

One::One()
{
     char z[] = "String";
     m = new char[strlen(z) + 1];
     strcpy(m, z);
     //a = 10; <---------------(2)
}

Now, I have stated that 'a' is const.  The compiler moans if I leave (2) as-is, stating that 'a' must be initialised in the initialisation list.  So, first question is why does a const variable *have* to be initialised in the initialisation list, not just in the constructor body ?

Secondly, if I change (1) to be a const char*, and try the strcpy as in the body of the constructor, the compiler moans about not being able to convert parameter 1 from const char* to char*.  Now, this problem vanishes when I initialise 'm' in the initialisation list (eg m("String") //unsafe I know ), again reinforcing the fact that const variables must be initialised in this manner.  But what I would liike to learn now is what if I wanted to leave 'm' as const; I obviously cannot safely assign to m a string literal (as above); the memory has to be allocated first hence my call to 'new'.  How would I go about this, or is the simple answer "I couldn't".

?

Cheers in advance.
0
mrwad99
Asked:
mrwad99
  • 6
  • 5
  • 4
  • +2
3 Solutions
 
AxterCommented:
>>So, first question is why does a const variable *have* to be initialised in the initialisation list

A constant value can not be modify.
The body of a constructor has to go by the same rules as any other member function, in that it can not modify constant objects.
The initialize list allows the class to set the value before the object is used, and before it enters the body of the constructor.

0
 
AxterCommented:
Example code:

class foo
{
public:
      foo(const char* Data):m(MyInitFunc(Data))
      {
      }
      ~foo(){delete (char*)m;} //Casting only needed for non-compliant compilers like VC++ 6.0
      const char* m;
private:
      const char* MyInitFunc(const char* Data)
      {
            char* p = new char[strlen(Data)+1];
            strcpy(p, Data);
            return p;
      }
};


int main(int argc, char* argv[])
{
      char somedata[] = "Hello Axter";
      foo myfoo(somedata);
      cout << myfoo.m << endl;
      
      system("pause");
      return 0;
}

0
 
AxterCommented:
You can safely get around the initialize problem for a const char* type by creating a initialize function.  The initiailize function can be a member function or a global function.

In the above example, I passed in a string to the constructor, which is then used in the initialize function to determine the length of the buffer to create, and the value to set the constant data.

According to the C++ standard, you should be able to delete a const pointer without having to cast away the constant.
However, some compilers are not fully compliant.  VC++ 6.0 has this problem.
The proper way to bypass this problem is to use const_cast.
Example:
~foo(){delete const_cast<char*>(m);}
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
AxterCommented:
Here's another method that uses less code, but requires more memory space per object.

class foo
{
      char *m_Real;
public:
      foo(const char* Data):m(m_Real)
      {
            m_Real = new char[strlen(Data)+1];
            strcpy(m_Real, Data);
      }
      ~foo(){delete m_Real;}
public:
      const char* &m;
};

The above class creates a constant pointer reference which is initialized to a non-constant pointer.
Your constuctor can then modify what "m" is pointing to, but external access is still constant since "m" is constant and m_real is private.

This method gives the object the ability to modify that object as needed, but outside exposure is still constant.
0
 
itsmeandnobodyelseCommented:
if you have a constructor that has a const char* as argument you don't need an initializer function to initialize the const member:

class A
{
private:
     const char* m_psz;

public:
     A(const char* psz) : m_psz(psz) {}
};

You shouldn't delete that pointer in the destructor because it is not very likely that it is created by 'new';

void main()
{
     A("anyConstantString");
}

Regards, Alex

0
 
AxterCommented:
>>if you have a constructor that has a const char* as argument you don't need an initializer function to initialize the const member:
>>A("anyConstantString");

This method is not safe.  It will work in most compilers, but it's undefined according to the C++ standard.

If the goal is to get a constant copy of the string that is passed in, then you do need to use one of the above methods I posted.

Also, if you're not passing in a string at all, as in the questioner's example code, then the above method is not pratical.
0
 
mrwad99Author Commented:
Axter,

>> foo(const char* Data):m(m_Real)


so essentially what you are doing here is to point a pointer at another pointer temporarily ?  

Is this safe ?  Does it not mean that m will be pointing to nothing ?

Cheers again.
0
 
itsmeandnobodyelseCommented:
A problem arises when the class instance lives longer than const char*, because the member pointer might become invalid.

A* f()
{
     A* pA = new A("Any Const String");
     return A;
}

void main ()
{
    A* pa = f();
   
    // Here the const char* member of pA might be invalid
   
    char buffer[100];
    strcpy( buffer, pA->getString() );   // This statement might crash

}

Regards, Alex
0
 
mrwad99Author Commented:
Alex,

I am aware of what you have said.  In the example you first gave,

 A(const char* psz) : m_psz(psz) {}

caused a crash because no memory is being allocated.  This is exactly the same in your above example.  What I want to know is in the example of Axter, is pointing a pointer at another pointer safe or not, if the pointer being pointed to is not initialised ?
0
 
sin_Commented:
without going into what's going on, I shall try to answer your questions.

1. So, first question is why does a const variable *have* to be initialised in the initialisation list, not just in the constructor body ?

>> As you know, for a const variable, initialization has to happen only when you declare it. The initialization list for a class ctor is different from an assignment.

say example:
class B;
class A {
A(const B& b1);
private:
B b
const int a;
};

so when you say:

A::A() {
b = b1;
}

You are actually calling the assignment operator of class B, which could be a really costly operation as compared to a simple initialization...so now you know that the initialiazation and assignment are different.

Now as you know the above, you should be able to point out that if you don't put your const data inside the initialization list, you will get an error because you are trying to assign something to your const data member which is wrong :-)

Now your second question:

Secondly, if I change (1) to be a const char*, and try the strcpy as in the body of the constructor, the compiler moans about not being able to convert parameter 1 from const char* to char*.  

>> in a scenario like this:
void foo(char* p);

//calling it,
foo("hello"); simply gives you a compilation error, becaue the string "hello" is a constant string. So if you change the char* p to const char* p, it will work.

guess you got your answer..never mind if you haven't gotten it yet...

you can pass a non-const param to a function which expects a const data. But you can't ever pass a const param to a function which expects non-const data.

Hope it helps!

-sin
0
 
itsmeandnobodyelseCommented:
the foo example works and is safe because both pointers m and m_Real are valid as long as the class instance exists and
both have the same pointer value.

There is a short moment in


     foo(const char* Data):m(m_Real)
     {
          m_Real = new char[strlen(Data)+1];
          strcpy(m_Real, Data);
     }

where both pointers have a pointer value that is not initialized. But with

    m_Real = new char[strlen(Data)+1];

pointers get valid. And this is safe, because no one could access the foo object before construction is done.
With

     foo(const char* Data):m_Real(NULL), m(m_Real)
     {
          m_Real = new char[strlen(Data)+1];
          strcpy(m_Real, Data);
     }

both members get initialized.
     
It is good practice to initialize all data members that have no default constructor in the initializer list.

I would recommend to use the initializer function and not two pointers because of two reasons:

1. both solutions will safely initialize a const member and the function is less tricky.
2. the purpose of a const member is to be const. if i need a member that must be changed why make it const?

Regards, Alex
   
0
 
mrwad99Author Commented:
Right, it seems that I have got three answers here all from different people that have all taught me something new !  Consequently I am going to split the points, but before I do...

sin_,

>>//calling it,
foo("hello"); simply gives you a compilation error, becaue the string "hello" is a constant string. So if you change the char* p to const char* p, it will work.

You say this is a constant string, fine.  But where exactly in memory is it ?  And how does the compiler know to alllocate 6 bytes (in this instance) ?  Is this not dangerous also ?  Also, in C programs especially (not so much C++; it has the string class), should I always (when declaring a char*) use new or malloc to allocate memory first ?  Or does it not matter ?

EG simple instances like

char* m= "Hello world";  // OK, or should I use malloc/new ??
printf(m);

Cheers again.
0
 
AxterCommented:
>>char* m= "Hello world";  // OK, or should I use malloc/new ??

You should not have a non-constant pointer pointing to a string literal.

This code is depricated according to the C++ standard.

You should use a constant poitner instead.
const char* m= "Hello world";

Some compilers will allow you to modify a string literal, and some compilers will crash.
Example, VC++ 5.0 will allow you to modify a string literal.  VC++6.0 will crash if you try to modify a string literal.
Modifying a string literal is considered undefined behavior according to the C++ standard.
0
 
sin_Commented:
hello,
Axter has given some tips to ya. So I won't cover that again. But I've something to add up.

You have found it yourself that you're allocating the memory on heap, so it may crahs later when you say char* p = "hello";
Because the ptr is pointing to a memory location on the stack. So it's not safe. ..
but when you say, cont char* p = "hello";, it's safe because the memory allocation happens at a place where all the const and global variables are kept.

Hope that helps
-sin
0
 
Mayank SAssociate Director - Product EngineeringCommented:
Hi Axter and others, Sorry this is out of this question, but could you throw some light on:

http://www.experts-exchange.com/Programming/Q_20816080.html

I'm sure your rich knowledge has a lot which you can share on that page....
0
 
mrwad99Author Commented:
<Increased points to 60 for a three way split>

>>cont char* p = "hello";, it's safe because the memory allocation happens at a place where all the const and global variables are kept.

So essentially this memory will not go out of scope until the program exits ?  

I still think it is bad form to pass a char* to a constructor and directly assign it to the char* member variable via the initialisation list as above; even if the char* being passed in is const hence will not go out of scope.  Correct ?
0
 
sin_Commented:
yeah you are right when you say that when you make your const char* point to a variable, the memory won't go out of scope.

for a raw char*, initialization isn't needed and you are right that it shouldn't be done.

-sin
0
 
itsmeandnobodyelseCommented:
Yes, any object should have it's own memory, so you should'nt store pointers but make a copy of the data you get as arguments.

The exception to this are container objects like arrays, maps, list or trees.

Regards, Alex
0
 
mrwad99Author Commented:
Thanks all for the help in this.  Splitting the points seemed the fairest thing I could do.

:)
0

Featured Post

How to Use the Help Bell

Need to boost the visibility of your question for solutions? Use the Experts Exchange Help Bell to confirm priority levels and contact subject-matter experts for question attention.  Check out this how-to article for more information.

  • 6
  • 5
  • 4
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now