1

I am new to C++ but have many years in C#, so excuse me if you see C# style coding, I am trying to grasp the concept of pointers, dereference, handles, references, array parameters and I still have difficulties.

The following is for an atmel microcontroller compiled with g++ 11 (arduino).

I am trying to pass a two-dimensionnal fixed size array of unsigned int as a parameter for a constructor of a base class, but no matter how I try, I can't get it transfer the array content in the base class. Here is the raw code, without the includes, only the essential.

In this exemple, the "Derived" constructor is called and I expect it to call the "DisplayDriver" constructor and pass the _colorTable array to it as a parameter. :

DisplayDriver.hpp

class DisplayDriver
{
public:
    DisplayDriver(const int xResolution, const int yResolution, const unsigned int colorTable[18][2]);
}

Derived.hpp

class Derived : public DisplayDriver
{
public:
    Derived(const int xResolution, const int yResolution);

private:
    const unsigned int _colorTable[18][2] =
    {
        {4, 2},
        {9, 31},
        ... // 16 other lines.
    }
}

Derived.cpp

Derived(const int xResolution, const int yResolution) : DisplayDriver(xResolution, yResolution, _colorTable)
{
    // Here, _colorTable[1][0] gives 9 and _colorTable[1][1] gives 31.  As expected.
}

DisplayDriver.cpp

DisplayDriver(const int xResolution, const int yResolution, const unsigned int colorTable[18][2])
{
    // Here, colorTable[1][0] return 0, not 9.
    // colorTable[1][1] return 0, not 31.
}

I tried many different parameter types for the array :

  • unsigned int (*colorTable)[18][2]
  • unsigned int colorTable[][2]
  • unsigned int (&colorTable)[18][2]
  • with or without "const"
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
sbeaudoin
  • 158
  • 2
  • 11
  • Using a two dimensional `std::vector` or `std::array` would be simpler. – Jesper Juhl Feb 25 '18 at 17:18
  • There is no such thing in arduino. Except at the expense of precious memory. I implement myself standard libraries only if there is no other way. – sbeaudoin Feb 25 '18 at 20:51

3 Answers3

3

The problem is nothing to do with types (which are correct). The issue is that you are using a data member in Derived in the constructor for DisplayDriver. When the DisplayDriver constructor is called the Derived object has not been constructed yet, so the values are garbage.

Simple solution is to make _colorTable static.

class Derived : public DisplayDriver
{
public:
    Derived(const int xResolution, const int yResolution);

private:
    static const unsigned int _colorTable[18][2];
};

and

const unsigned int Derived::_colorTable[18][2] =
{
    {4, 2},
    {9, 31},
    ...
};
john
  • 85,011
  • 4
  • 57
  • 81
  • I understand. I thought _colorTable being in the hpp file of derived, is initialized before calling the constructor of base. In other words, I thougt Derived constructor will be complete before callind base. By making it static, _colorTable WILL BE initialized before all that. Thanks. – sbeaudoin Feb 25 '18 at 20:57
  • If I make it static, I have a message (translated) "a member of type 'const unsigned int _colorTable[18][2]' can't have a class initializer". I will look for that now. Not easy... – sbeaudoin Feb 25 '18 at 21:07
  • @sbeaudoin You need to separate the declaration from the definition, see the code I posted. – john Feb 25 '18 at 21:10
  • @sbeaudoin The declaration goes in your header file, but the definition (where the values get initialised) must go in a cpp file. – john Feb 25 '18 at 21:11
  • Yes, that's what I did and it worked perfectly. I must remember that the hpp file is not the one who is compiled and it is not the same thing as "partial class" in the C# world. That's what I thought first. – sbeaudoin Feb 26 '18 at 11:13
2

As others have said, your problem is that your using _colorTable before it has been initialised. If you don't mind all instances Derived sharing a common _colorTable (which is what static in the context of a class declaration means) then making _colorTable static works.

If you however want each instance to have its own _colorTable then one way to get around your problem is to use the base-from-member idiom. This idiom utilize the initialisation order of base classes. When a class has multiple base classes...:

class A {  };
class B {  };
class C {  };

class D : public A, public C, public virtual B {  }; // note virtual

...C++ says that the initialisation order is:

  1. All virtual base classes in the order of declaration (left to right).

  2. All non-virtual base classes in the order of declaration (left to right).

Therefore if you if create an instance of D then D's base classes gets initialised in the following order: first B, then A and then C. If your just starting out (and perhaps more so if you're using C++ on microcontrollers) then you don't have have to worry about virtual base classes.

So in your case, using your knowledge of base class initialisation order, you can use the base-from-member idiom. Create a new class and move _colorTable from Derived to your new class:

// Derived.hpp

// namespace detail {

class DerivedDMembers 
{
protected:
  const unsigned int _colorTable[18][2] =
  {
    {4, 2},
    {9, 31},
    ... // 16 other lines.
  };
};

// } // namespace detail

// if you feel that DerivedDMembers is polluting your namespace
// you can put it in a namespace called "detail". there's nothing
// special about 'detail' other than that "internal details" by
// convention are put in it.    

Have Derived derive from your new class (DerivedDMembers):

// Derived.hpp

// note how DerivedDMembers is listed before DisplayDriver, which
// is crucial
class Derived : protected DerivedDMembers, public DisplayDriver
{
public:
  Derived(const int xResolution, const int yResolution);

  // note, _colorTable is now in DerivedDMembers  
};

Now you can safely pass _colorTable to your base class DisplayDriver:

// Derived.cpp

Derived(const int xResolution, const int yResolution) : DisplayDriver(xResolution, yResolution, _colorTable) {  }

You don't have to explicitly call DerivedDMembers's default constructor in Derived's constructors initialiser list because if a base class constructor call is omitted its default constructor is implicitly called.

Now each instance of Derived has its own _colorTable and you can use _colorTable to initialise DisplayDriver.

  • Thank you for the precision and for explaining another route. But there will be only one display attached, so only one color table. I will keep what you wrote in mind in case I need it later. – sbeaudoin Feb 26 '18 at 11:08
  • No problem. Another thing about your code, which isn't related to your problem: In `DisplayDriver`'s constructor you take a `const unsigned int colorTable[18][2]`. With that signature it'll accept any array of size `[n][2]` (for `n` > 0). If you only want arrays of size `[18][2]` then one solution is to take a `const unsigned (&colorTable) [18][2]` instead; a _reference_ to an array. Read more [here](https://stackoverflow.com/questions/1328223/when-a-function-has-a-specific-size-array-parameter-why-is-it-replaced-with-a-p). Also, the type `unsigned` is the same type as `unsigned int`. –  Feb 26 '18 at 12:00
  • Really? The compiler will not check the array size? If will follow your suggestion concerning using a reference for the parameter. Thanks. – sbeaudoin Feb 26 '18 at 17:31
1

The problem here is the order of initialization:

Derived instance;

is expanded to:

  1. Call to constructor Derived::Derived()
  2. To construct derived class the base classes must be constructed first. So DisplayDriver(xResolution, yResolution, _colorTable) is run.
  3. All member variables are constructed with their constructors in order in which they were declared in the class. In this case you specified that instance._colorTable is initialized to that array.
  4. Body of Derived::Derived is executed.

So, in 2. you are using an uninitialized variable which is undefined behaviour - it can contain anything. In your case the variable contains array of zeroes.

To fix this you can mark _colorTable static.

Also there's no need to use const for int as it is passed by value.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • Concerning const, I don't really understand all the flavors of it yet, so I put it everywhere I can until it don't work... – sbeaudoin Feb 25 '18 at 21:00
  • @sbeaudoin Yes, it might not be intuitive at first especially in more complicated cases with pointers and such. Especially comming from C# background, but it is very useful feature. Regarding your function: the int is passed by value=by copy so the function cannot change the variable you pass to the function. Only thing it does is that you cannot change the value of the new temporary variable/parameter which will destroyed at the end of the function anyway. – Quimby Feb 25 '18 at 21:32