Powiedzmy, że mam 2 struct s:

typedef struct
{
    uint8_t useThis;            
    uint8_t u8Byte2;             
    uint8_t u8Byte3; 
    uint8_t u8Byte4;  
} tstr1

I

typedef struct
{
    uint8_t u8Byte1;            
    uint8_t u8Byte2;             
    uint8_t useThis;  
} tstr2

Będę tylko potrzebował elementu useThis wewnątrz funkcji, ale w niektórych przypadkach będę musiał rzutować jedną lub drugą strukturę:

void someFunction()
{
    someStuff();
    SOMETHING MyInstance;
    if(someVariable)
    {
        MyInstance = reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE); //This line of course doesn't work
    }
    else
    {
        MyInstance = reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE); //This line of course doesn't work
    }
    MyInstance->useThis; //Calling this memeber with no problem

    moreStuff();
}
  • Więc chcę użyć useThis bez względu na to, co zostało zrobione. Jak to zrobić?

  • Chcę uniknąć someFunction() bycia szablonem (tylko po to, aby uniknąć tego rodzaju rzeczy)

  • Zwróć uwagę na pytania takie jak to mają podobny problem, ale elementy struct mają tę samą kolejność

EDYTUJ:

W RealLife struktury te są znacznie większe i mają kilku członków o takich samych nazwach. Bezpośrednie rzucanie uint8_t jako reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE)->useThis byłoby nużące i wymagałoby kilku reinterpret_casts (chociaż jest to działające rozwiązanie dla mojego pytania przed tą EDYCJĄ). Dlatego nalegam, aby MyInstance był „kompletny”.

4
Ivan 24 czerwiec 2021, 15:55

4 odpowiedzi

Najlepsza odpowiedź

Korzystanie z wysyłania virtual zwykle nie jest tym, czego oczekujesz podczas mapowania na sprzęt, ale jest alternatywą.

Przykład:

// define a common interface
struct overlay_base {
    virtual ~overlay_base() = default;
    virtual uint8_t& useThis() = 0;
    virtual uint8_t& useThat() = 0;
};

template<class T>
class wrapper : public overlay_base {
public:
    template<class HW>
    wrapper(HW* hw) : instance_ptr(reinterpret_cast<T*>(hw)) {}
    uint8_t& useThis() { return instance_ptr->useThis; }
    uint8_t& useThat() { return instance_ptr->useThat; }

private:
    T* instance_ptr;
};

Dzięki temu możesz zadeklarować wskaźnik do klasy bazowej, przypisać go i użyć po instrukcji if:

int main(int argc, char**) {

    std::unique_ptr<overlay_base> MyInstance;

    if(argc % 2) {
        MyInstance.reset( new wrapper<tstr1>(INFO_FROM_HARDWARE) );
    } else {
        MyInstance.reset( new wrapper<tstr2>(INFO_FROM_HARDWARE) );
    }

    std::cout << MyInstance->useThis() << '\n';
    std::cout << MyInstance->useThat() << '\n';
}

Próbny

Wyjaśnienie dotyczące mojego komentarza: "Działa, ale jeśli kompilator nie jest naprawdę sprytny i nie potrafi zoptymalizować wirtualnego wysyłania w twoich wewnętrznych pętlach, będzie wolniej, niż gdybyś poświęcił czas na wpisanie cast ":

Pomyśl o wysyłce virtual jako posiadającej tabelę przeglądową (vtable) używaną w czasie wykonywania (co często się dzieje). Podczas wywoływania funkcji virtual program musi użyć tej tabeli wyszukiwania, aby znaleźć adres rzeczywistej funkcji składowej do wywołania. Kiedy nie można zoptymalizować wyszukiwania (jak upewniłem się w powyższym przykładzie, używając wartości dostępnej tylko w czasie wykonywania), zajmuje to kilka dodatkowych cykli procesora w porównaniu z tym, co uzyskasz wykonując rzutowanie statyczne.

3
Ted Lyngmo 25 czerwiec 2021, 07:58

Proste odwołanie do elementu członkowskiego struct może być tym, czego potrzebujesz:

uint8_t &useThis=SomeVariable
 ?reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE)->useThis
 :reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE)->useThis;
3
Nordic Mainframe 24 czerwiec 2021, 13:24

Jak zasugerował AndyG, co powiesz na std::variant (nie ma wzmianki o używanym standardzie c++, więc może rozwiązanie w c++17 jest w porządku - warto również użyć , jeśli nie ma dostępnego c++17 ).

Oto przykład

std::variant wie, jaki typ jest w nim przechowywany i możesz użyć odwiedzin w dowolnym momencie, gdy chcesz użyć dowolnego z członków w tym miejscu (fragment tutaj dla jasności):

// stolen from @eerrorika (sorry for that :( )
struct hardware {
    uint8_t a = 'A';
    uint8_t b = 'B';
    uint8_t c = 'C';
    uint8_t d = 'D';
};

struct tstr1 {
    uint8_t useThis;            
    uint8_t u8Byte2;             
    uint8_t u8Byte3; 
    uint8_t u8Byte4;  
};

struct tstr2 {
    uint8_t u8Byte1;            
    uint8_t u8Byte2;             
    uint8_t useThis;  
};

// stuff

if(true)
{
    msg = *reinterpret_cast<tstr1*>(&hw);
} 
else
{
    msg = *reinterpret_cast<tstr2*>(&hw);
}

std::visit(overloaded {
    [](tstr1 const& arg) { std::cout << arg.useThis << ' '; }, 
    [](tstr2 const& arg) { std::cout << arg.useThis << ' '; }
}, msg);

EDIT: możesz również zrobić wariant wskaźników EDIT2: zapomniałem uciec z niektórych rzeczy...

4
ben10 24 czerwiec 2021, 13:43

Do tego służą szablony:

template<class tstr>
std::uint8_t
do_something(std::uint8_t* INFO_FROM_HARDWARE)
{
    tstr MyInstance;
    std::memcpy(&MyInstance, INFO_FROM_HARDWARE, sizeof MyInstance);
    MyInstance.useThis; //Calling this memeber with no problem
    // access MyInstance within the template
}

// usage
if(someVariable)
{
    do_something<tstr1>(INFO_FROM_HARDWARE);
}
else
{
    do_something<tstr2>(INFO_FROM_HARDWARE);
}

Chcę uniknąć niektórychFunction() jako szablonu (tylko po to, aby uniknąć tego rodzaju rzeczy)

Dlaczego nie mogę oddzielić definicji mojej klasy szablonów od jej deklaracji i umieścić ją w pliku .cpp?

Powiązany problem nie jest problemem dla twojego przypadku użycia, ponieważ potencjalny zestaw argumentów szablonu jest zbiorem skończonym. W następnym FAQ wpis wyjaśniono, jak: Użyj jawnych instancji szablonu.

5
eerorika 24 czerwiec 2021, 13:40