Jump to content
43oh

Declare an object of a base class and change it to an object of a derived class during setup?


Recommended Posts

Hi,

 

I just don't know how to deal with the following challenge:

I want to have a global object that is accessed by multiple functions, that are called in the loop. But I want to decide, wich class it comes from during setup.

 

To make things a little bit more clear, I'm working on a remote control for Mixing Desks. There are a lot of manufacturers out there, all having their own concept on how to remote their desks. But the parameters I access are always the same. So I wrote a base class "MixingConsole" that has the Methods like setLevel, setPan... that are completely empty and then wrote classes for explicit Models that are derived from the base class MixingConsole and define what happens, if for an example setLevel is called (in one case, something is sent via ethernet, in other cases a Midi signal is sent...).

 

Now, I'd like to create a global Object of the type MixingConsole and decide wich kind of Console it is during setup. I'd like to do something like this:

MixingConsole m1;

setup(){
  <some user input for checking wich console should be remoted, decision is stored in Variable d>
  
  switch (d) {
    case 0: m1=YamahaCL
    case 1: m1=SoundcraftVI
    case 2: m1=AvidS3L
    case 3: m1=DiGiCoSD
  }
  
  m1.begin();
}

loop(){
  m1.sendLevel(channel, level);
  ...
}

I know that my switch case statement is no valid c code, but it should outline what I want to do. I'm not sure if it is possible to declare an object of a base class and later make it to an object derived of this class. How may this be done correctly?

Link to post
Share on other sites

I think this is possible. What you do is create a pointer of type base class and instantiate a subclass then assign the base class ptr to it, but make sure all base class methods are declared "virtual".

 

On a phone with 2 toddlers in tow right now so I can't test an example but be sure your base class ptr accesses methods and variables using "->" instead of "."

 

In C++ terms a virtual table lookup occurs every time you access the subclass instance.

Link to post
Share on other sites

Thank you, that solved my problem!

 

Now if I want to set the pointer during Setup and access it from the loop, the instances of each subclass it possibly could point at, have to be global objects, defined outside the setup function, right? Now after I decided to use one of them for the rest of the time during setup, I won't need the others anymore. I don't want to waste a lot of memory for these unused objects. As I don't think that the destructor is ever called, would manually call the destructor of the unused objects after the decision wich one I should use be good to free memory? I read somewhere that manually calling the destructor could lead to unexpected errors in some cases.

 

Example, similar to the first one:

//global Objects
MixingConsole* m1;
YamahaCL CL1;
SoundcraftVI VI1;
AvidS3L S3L1;
DiGiCoSD SD1;

setup(){

...

  switch(d) {
    case 0: {
      m1= &CL1;
      delete VI1;
      delete S3L1;
      delete SD1;
    }
    case 1: {
      m1= &VI1;
      delete CL1;
      delete S3L1;
      delete SD1;
    }
   ...
  }
  
  m1->begin();
}

loop(){
  m1->sendLevel(channel, level);
  ...
}
Link to post
Share on other sites

You should use the "new" keyword to declare what you want. In general folks sneer at the use of "dynamic" memory allocation in an embedded application, but if it's a memory allocation you do once during setup and never destroy or create more, I can't see the problem.

 

Besides that globally allocated objects won't give up their memory contents even if you did run their "destructor". Only dynamically allocated objects are capable of this.

 

IIRC the syntax is like:

 

BaseClass *bptr;

bptr = new SubClass();

 

If SubClass supports arguments to its constructor you can put them in the parentheses.

 

Energia does implement the "new" keyword using the libc's built in malloc() heap allocator.

Link to post
Share on other sites

Great, that works!

 

So to understand whats happening: When I call pMySubClass=new SubClass(), a new instance of SubClass is created and the address of its memory is stored in pMySubClass. But different to just calling SubClass mySubClass; and getting the memory address via pMySubClass= &mySubClass, the instance created witch new SubClass() exists until I call delete while mySubClass is deleted at the end of the function?

As dynamic memory allocation does not work in the embedded environment without OS, creating and deleting instances multiple times in this way would normally cause troubles, but that is no problem here, since my instance is used and never deleted until power down?

 

Please correct me if I got it wrong.

Link to post
Share on other sites

So to understand whats happening: When I call pMySubClass=new SubClass(), a new instance of SubClass is created and the address of its memory is stored in pMySubClass. But different to just calling SubClass mySubClass; and getting the memory address via pMySubClass= &mySubClass, the instance created witch new SubClass() exists until I call delete while mySubClass is deleted at the end of the function?

 

Yes, that's basically what's happening.

 

As dynamic memory allocation does not work in the embedded environment without OS, creating and deleting instances multiple times in this way would normally cause troubles, but that is no problem here, since my instance is used and never deleted until power down?

 

To be clear, dynamic allocation doesn't require an OS. The "new" and "malloc" functionality is provided by the Runtime Support Library, which also contains the initialisation code that runs before main(). That library is included with the C/C++ compiler and gets built in to your project automatically. On embedded platforms sometimes the heap size is set to zero by default, however, and that needs to be increased to allow dynamic allocation. I guess that's already done if you're using energia.

 

The problem is more to do with what happens to the heap structure used to manage the dynamic allocations.

 

If your program is continually allocating and freeing memory while it runs you run the risk of forgetting to free some memory. When that happens you end up with less free memory available until the next reset. It's possible for all the free space in the heap to leak away, and eventually no allocations are possible.

 

Also, allocating and freeing memory during runtime can cause the free space on the heap to become fragmented to the extent that a big allocation fails. If you allocate a couple of different memory blocks like so:

A *a = new A(); // size = 4
B *b = new B(); // size = 2

Then a heap of size 8 might look like this ("|" marks the start and end of the heap):

 

|aaaabb__|

 

Now try this:

delete a;
C *c = new C(); // size = 6

The heap would end up like this after the delete:

 

|____bb__|

 

Now there's six spaces free, but they're not in a contiguous block so new C() will fail. Different patterns of allocation and deletion can produce different results. In this case the program would work if b got allocated before a. Unconstrained patterns of allocation and deletion make it hard to work out whether a particular allocation will succeed. You need to inspect the internal state of the heap or know the full history of allocation/release to find out whether the free space is all in one block or not.

 

Finally, the allocator might take longer to return when the memory gets fragmented. That's no good for real-time applications.

 

For these reasons it's common to avoid allocating and deleting memory during runtime in embedded software. That can be done by never using new/malloc, but other options are available.

 

As spirilis pointed out, it's fine to allocate memory dynamically at startup if you know it's needed for the whole lifetime of the program. If there's no deallocation the heap can't get fragmented and there's no risk of memory leaks if all the allocation is done at startup.

 

The other pattern that avoids fragmentation is where later allocations are always deleted before earlier ones. That's similar to the way local variables get stored on the stack. A stack keeps all the used memory at one end and the free memory at the other, so it can't get fragmented. You just have to be sure there's enough stack space for the maximum possible amount used.

Link to post
Share on other sites

Totally what @@tripwire said.  As a side note, this is why you'll find Arduino forums littered with folks urgently requesting that you NOT use the "String" class for anything.  String class operations necessarily go wild dynamically allocating and deallocating (I think...) memory ad hoc throughout the firmware's runtime as long as any routine part of your firmware sketch uses the String class's features.  It's classic for blowing out the stack and causing MCUs to crash.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...