Re: static array initialization in a class

From: Fallout boy (false_at_ddress.invalid)
Date: 03/18/04


Date: 18 Mar 2004 06:59:11 -0500


> class A{
> public:
> int *arr;
> int n;
> A(int nn, const int* narr):n(nn)
> {
> for(int i=0 ; i<n; i++)
> arr[i]=narr[i];
> }
> A(int nn):n(nn){};
> };
>
> class B:public A{
> public:
> const static int init_nums[]={1,2,3};
> const static int init_n=3;
> B():A(init_n, init_nums){
> //..
> }
> };
>
> int main (int argc,char ** args){
> B b;
> }
>
> It does compile but returns a linker error: undefined reference to
> `B::init_nums'.
> Maybe I would understand why, if it returned the same error for
> 'B::init_n'.
> But it doesn't. And it links well when the B's constructor is like
> this:
> B():A(init_n){};

The first tip I want to offer you is to use more meaningful names for your
variables, and use comments. It makes it pretty difficult for someone who
isn't privy to what your class is for ("A" is meaningless), and didn't
participate in figuring out the logic for the methods, to jump in and follow
what your code is trying to do.

It also makes it really hard for yourself if you are writing a big program.
No commenting and every class having methods and data members with
meaningless names makes it almost impossible to properly debug a large or
complicated program. If not impossible, easier or quicker to just write a
new program.

Look at your converting constructor for class A:
   A(int nn):n(nn){};

That's ridiculous! Imagine if you had several data members to initialise
and they all had nonsense names such as a, b, c, etc. It's very easy to
lose track of what's happening. Use mnemonic names! Compare these:

class Person {
    private:
        unsigned int Age;
        float Height;
        string Name;
    public:
        void setname( string in_name ){ this->Name = in_name; } // sets name
to argument
        void setAge( unsigned int in_age ){ this->Age = in_age; } // sets
Age to argument
        void setHeight( float in_Height ){ this->Height = in_height; } //
sets Height to argument
        Person( string in_name, unsigned int in_age, float in_height ): //
initialise
            Name(in_name), Age(in_age), Height(in_height){}
};

class XYZ
    public:
        unsigned int i;
        float f;
        string s;
        void seti( int ii ) { i = ii; }
        void setf( float ff ){ f = ff; }
        void sets( string ss) { s = ss; }
        XYZ( string ss, unsigned int ii, float ff ):
            s(ss), i(ii), f(ff){}
};

XYZ is completely unintelligible, yet it's almost identical to Person.
Those members could be for anything at all, and even though the code is
fairly simple, it takes a bit of work to follow. Not so class Person - it's
totally intuitive, and infinitely easier to debug. It's a bit more typing,
and it's a bit more thinking to use meaningful names, but it's a lot less
cursing and crying when the executable does something funny.

Also you'll notice that Person has its data members all private - and that's
good programming practice. Don't allow the rest of your program have
unfettered access to the internal workings of your classes, as this will
only encourage poor programming. Object-oriented programming is concerned
with code recycling. If you have a main function which tinkers with your
class's internal workings, and then decide to change how the class is
implemented, you have to also change the main function. If instead you only
allow access to the class via public functions, you can do whatever you like
to the class without breaking existing code (provided you keep the public
functions intact!)

Also, you declare a pointer and use it as an "array". That's a bad idea,
unless you are using it to store the address of dynamically allocated memory
returned from the new or new[] operators. It isn't an array!!! Let me
stress that. Imagine the area of memory to which your pointer is pointing.
Let's say your memory looks like this:

|000|001|002|003|004|005|006|007|008|009|00A|00B| (addresses in mem)
| | | | | | * | * | | | * | |
| (all empty except those with *)

int *arr;

For starters, you don't initialise arr. It is pointing somewhere at random.
The compiler shouldn't pull you up on this, and it appears that it hasn't.
However, it's very poor programming practice, and dangerous, too. You could
hang your system with this program, as that pointer could be pointing at
memory used by another program. However, assuming arr is pointing safely:

|000|001|002|003|004|005|006|007|008|009|00A|00B| (addresses in mem)
| | | | | | * | * | | | * | |
| (arr points to location 000)
   ^arr points here

Here you have several empty blocks, all of which contain logical garbage at
the start of your program. The blocks with '*' in them are currently
holding variables declared elsewhere in your program. Using 'arr' as a de
facto array name is dangerous - after just five ints are inserted, you will
then overwrite location 005. Some other variable was stored there, and now
its value is changed. Your program could behave in an unexpected way.

Even if those blocks were totally empty before you declared the int*,
subsequent declarations might use up those blocks. Immediately after the
declaration of int *arr, you declare int n.

int n;

|000|001|002|003|004|005|006|007|008|009|00A|00B| (addresses in mem)
| | n | | | | * | * | | | * | |
| (arr points to location 000, n is in block 001)
   ^arr points here

Now "n" is overwritten as soon as you try to insert 2 ints to your "array".
Your array and your other variables will keep overwriting each other. Let's
say you insert 77, 88, 99 into your array.

|000|001|002|003|004|005|006|007|008|009|00A|00B| (addresses in mem)
| 77 | 88 | 99 | | | * | * | | | * | | |
   ^arr points here

Later in the program when "n" is initialised, the second "array" member is
overwritten. Let's say "n" is assigned the value 10:

|000|001|002|003|004|005|006|007|008|009|00A|00B| (addresses in mem)
| 77 | 10 | 99 | | | * | * | | | * | |
|

Now you'll go to use your "array", and if you were expecting to see the
value 88 in block 001, you'll be surprised. This is why it's dangerous
practice to use pointers in this way. You have no guarantee that the memory
blocks after the one to which your pointer is pointing (in this case, 001 -
00B) are free.

However, if you declare an array of int, rather than a pointer to int, you
get a guarantee that those areas you ask for ( eg int arr[10] asks for 10
blocks) will be vacant, and won't be later used.

int arr[10];

|A00|A01|A02|A03|A04|A05|A06|A07|A08|A09| (addresses in mem)
| | | | | | | | | |
| (all empty)

You can use this freely, and have no fear of overwriting.

You could also have the best of both worlds, by using:

int *arr = new int[10];

but note that it's still dangerous to use the array dereference beyond
arr[9]. If you're looking for the flexibility of dynamic memory allocation,
then use some linked structure, or use an STL type such as <deque> or
<queue> or <list>.

As regards your question, sorry, can't really help you too much. The first
invocation, the one that failed, called a different constructor. Perhaps
you should write a more complicated main function which tries to use the
members of B in a few different contexts. You have the constructor for A
executing, then B, as B inherits from A. There's a fair bit going on, so
why not just use a vanilla constructor for both classes and see what you can
do via main to test where the program breaks down.

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated. First time posters: Do this! ]



Relevant Pages

  • Re: NotSupportedException TomTomSDK
    ... The easiest way to marshal this data is as a byte array. ... use the System.Text.Encoding.Unicode.GetString to extract the inline string. ... BitConverter will allow you to get the int members out of the byte ... > Any ideas how to declare the TCHAR? ...
    (microsoft.public.pocketpc.developer)
  • Re: why this program is Crashing
    ... Here you define arr to be an array of int with a length of 100. ... That would be a piece of memory just big enough to contain a pointer with that memory containing information about the location of an int. ... an lvalue of array type gets automatically converted into a pointer to the first element of the array. ...
    (comp.std.c)
  • Cursor operations
    ... DECLARE @array TABLE (nameID INT NOT NULL, ... DECLARE arrayCursor CURSOR SCROLL FOR SELECT nameID, ... DECLARE @index INT, @count INT, @offset INT ...
    (microsoft.public.sqlserver.programming)
  • Re: Warning on assigning a function-returning-a-pointer-to-arrays
    ... declare pfunc as function returning pointer to array 5 of int ... I used cdecl myself to figure out how to declare a function ...
    (comp.lang.c)
  • Re: Ada exception block does NOT work?
    ... Array size is probably the simplest example - you can declare an array inside a procedure whose size is determined by a parameter to that procedure. ... int& m_Count; ... Gods of Heaven, gods of Earth, ...
    (comp.lang.ada)

Loading