1

working on implementing an Serial receive library for a specific hardware sending information to a ESP8266 device, I came across the following issue

for some background:

  • I use sloeber the eclipse IDE for arduino programming, with Arduino IDE the same issue exists
  • __cplusplus gives me 201103, so I assume I am on c++11

explanation of the setup:

  • I have derived classes which represents interpreted packages received from serial
  • these classes are all derived form on base class which implements some common methods, here methodA (in reality: length of data, and getter for the data)
  • to forward these packets around I have created a class which has a member of a struct (sData) which has a tagged union inside. for simplicity I only use sData here not the class containing it.
  • the union uUnion is the one holding the packets content in form of derived packages, only one at a time, but able to contain every derived class available.
  • i do not use any dynamic object creation (no new), to prevent memory leaks

maybe there are better solution to this problem. Ideas are appreciated. But I would like to focus on why my implementation is not working

problem

the usage of the members-functions of the derived classes out of the union. I can call them directly without problem. But I am not able to create a pointer out of the union to the derived class instance and call that member.

//this is the base class
class cBaseA{
public:
    virtual void methodA(void){
            Serial.print(" A ");
            Serial.println(i);
        }
    int i; //some attribute to work with
private:
};
//first derived class
class cDerivedA: public cBaseA{
public:
     void methodA(void) {
        Serial.print(" DerivedA ");
        Serial.print(i);
        Serial.print(" ");
        Serial.println(ii);
    }
     int ii; //additional attribute
private:
};
//second derived class
class cDerivedB: public cBaseA{
public:

     void methodA(void) {
        Serial.print(" DerivedB ");
        Serial.print(i);
        Serial.print(" ");
        Serial.println(ii);
    }
    int ii;
private:
};
//third derived class
class cDerivedC: public cBaseA{
public:
     void methodA(void) {
            Serial.print(" DerivedC");
            Serial.print(i);
            Serial.print(" ");
            Serial.println(ii);
        }
     int ii;
private:
};
//this is the structure to pass different derived instances around
struct sData{
    enum eDataType{
        eunkown,
        eDerivedA,
        eDerivedB,
        eDerivedC
    } DataType;
    union uUnion{
        cDerivedA DerivedA;
        cDerivedB DerivedB;
        cDerivedC DerivedC;
        ~uUnion(){};
        uUnion(){};
    } ;
    uUnion DataUnion;
    sData(void){DataType=eDataType::eunkown;};
    sData(const sData &Data){
        this->DataType=Data.DataType;
        switch(this->DataType){
            case eDataType::eDerivedA:
                this->DataUnion.DerivedA=Data.DataUnion.DerivedA;break;
            case eDataType::eDerivedB:
                this->DataUnion.DerivedB=Data.DataUnion.DerivedB;break;
            case eDataType::eDerivedC:
                this->DataUnion.DerivedC=Data.DataUnion.DerivedC;break;
            case eDataType::eunkown:
                break;
        }
    }
    ~sData(){};
};


void DataFunction(struct sData *Data){
    Serial.println("A1:");
    Data->DataUnion.DerivedB.methodA();   //works fine

    cDerivedB *DerivedB;
    DerivedB=&(Data->DataUnion.DerivedB); //this works
    DerivedB->methodA();                  //compiles, but execution exception, code 28

}

void setup(){
    Serial.begin(9600);
    delay(2000);
sData Data;
    cDerivedB DerivedB1;
    DerivedB1.i=1;
    DerivedB1.ii=2;
    Data.DataType=sData::eDataType::eDerivedB;
    Data.DataUnion.DerivedB=DerivedB1;
    DataFunction(&Data);

}

what I tried so far:

  • the absence of the virtual destructor of cBaseA has no influence (I tried already with it)

  • to make the union anonymous did not changed anything

  • to make a reference to the unions content results in the same error:

    cDerivedB &DerivedB; DerivedB=&(Data->DataUnion.DerivedB); DerivedB.methodA();

  • I am able to make copy to out of the union to the base class, but this causes slicing, and the call ends in the base class, not as I need in the derived class

the question is: why does this exception happen, if the direct call is possible?

What is the right way to get a handle (pointer, reference) of the unions content and call the member? I know that there are discussions out there, that unions should only contain simple data types. Is this just a flaw of the compiler (c+11) letting me write this? But still, direct access is possible. Why not via pointer?

many thanks in advance if somebody is able to put that cloud away I can not see through.

  • Your program exhibits undefined behavior, by way of accessing an object before its lifetime has started. You never construct any member of the union, and they are all non-trivially-constructible types. Practically speaking, this means the vtable pointer is never initialized to point to the correct vtable, and that's why the virtual call is crashing. Union of non-trivial types is very finicky to work with. – Igor Tandetnik Jan 09 '21 at 16:59
  • in my understanding the lifetime of the union content starts in the 4.th line of setup: "cDerivedB DerivedB" then it is passend to the union, later it is get form there - why should it not be started? if this is undefined behavior, why the direct call of unions content works (second line of DataFunction), but not with the pointer – HubertMaier Jan 09 '21 at 17:05
  • The line `cDerivedB DerivedB1;` in `setup` constructs a local variable, not any member of `Data.DataUnion`. The line `Data.DataUnion.DerivedB=DerivedB1;` then attempts to call a copy-assignment operator on the object `Data.DataUnion.DerivedB` that has never been constructed - this exhibits undefined behavior. – Igor Tandetnik Jan 09 '21 at 17:09
  • The direct call works by accident; the compiler managed to de-virtualize it, so it doesn't go through the vtable. It's still undefined behavior, you just happen to get away with it. "Seems to work" is one possible manifestation of undefined behavior. – Igor Tandetnik Jan 09 '21 at 17:12
  • ok. thanks for clearification. I will now try to split data and methods, and see if I can finde a solution – HubertMaier Jan 09 '21 at 19:21
  • can somebody explain, whats would be a correct construction of the union member? as copying seems to be not. like 'cDerivedB DerivedB1' is a local variable. but is this not a valid object? i can do everything with DerivedB1 I want from it. Call members, assigning attributes, copying works, even inheritance works. What would be correct construction?. Is there a construct for unions which do not need to be constructed (struct for example?) – HubertMaier Jan 09 '21 at 19:39
  • You construct a union member with placement new, as in `new(&Data.DataUnion.DerivedB) cDerivedB(DerivedB1);` . Every time you switch members, you should explicitly destroy the current one ( `Data.DataUnion.DerivedB.~cDerivedB();` ) then construct the new one. You don't need to do all that if you have a union of trivially constructible types (basically, types like `int` or a C-style struct that doesn't require any particular initialization) - but `cDerivedB` is not in fact trivially constructible. – Igor Tandetnik Jan 09 '21 at 21:24
  • See also [this question](https://stackoverflow.com/questions/30492927/constructor-and-copy-constructor-for-class-containing-union-with-non-trivial-mem) – Igor Tandetnik Jan 09 '21 at 21:26

0 Answers0