Vector range über binäre objekt grenzen



  • Guten Tag,

    also ich versuche einen weg zu finden um eine cevtor range (begin, end) über eine binäre grenze zu schaffen.

    Also ich habe in einem shared object eine methode die in etwa diese signatur hat
    void bla(Object*, int numberOfObjects, int sizeOfObject);

    Da die Shared lib mit mehreren Versionen von Object umgehen soll (also zb.):

    // V1
    struct Object {
      int i;
    }
    
    // V2
    struct Object2 : public Object {
      int b;
    }
    

    fällt mir keine c++sigere Lösung ein als diese pointer frickelei mit der momentanen size des structs.
    So kann die shared lib mit allen objecten des structs aus V1 umgehen (somit halt auch mit allen nachfolgenden).

    Ich hatte überlegt ob man dies mit CRTP irgendwie lösen kann, aber ich bin nicht zum durchbruch gekommen so das man die Shared lib auch mit neueren versionen verwenden kann.

    Also die gewünschte funtionssignatur wäre sowas wie
    void bla(std::vector<Object>::iterator beg, std::vector<Object>::iterator end);
    oder was ähnlich c++siges :p

    Hoffe ihr versteht mich..



  • fritz-cola schrieb:

    Also ich habe in einem shared object eine methode die in etwa diese signatur hat
    void bla(Object*, int numberOfObjects, int sizeOfObject);
    So kann die shared lib mit allen objecten des structs aus V1 umgehen (somit halt auch mit allen nachfolgenden).

    Das würde ich bezweifeln wollen, da Object und Object2 unterschiedlich groß sind. Du musst also schon in dem Bla-Code wissen, wie groß die sind. Wenn, dann bräuchtest du also ein Array auf Pointer, also Object**.

    Kannst du nicht eine kleine Template-Funktion in den Header tun, die mit vector<unique_ptr<Object>> als auch mit vector<unique_ptr<Derived>> klarkommt? Diese kann ganz einfach bleiben und nur eine Library-Funktion aufrufen, die dann einfach ein Pointer auf Base nimmt.

    #include <iostream>
    #include <memory>
    #include <vector>
    using namespace std;
    
    // V1
    struct Object {
        int i = 42;
    };
    
    // V2
    struct Object2 : public Object {
        int b;
        Object2() { i = 23; }
    };
    
    void librayFunction(const Object *o) {
        cout << o->i << '\n';
    }
    
    template <typename It>
    void bla2(It beg, It end)
    {
        for (auto it = beg; it != end; ++it) librayFunction(it->get());
    }
    
    int main()
    {
        vector<unique_ptr<Object>> vo1;
        vo1.push_back(make_unique<Object>());
        vo1.push_back(make_unique<Object2>());
        bla2(vo1.begin(), vo1.end());
    
        vector<unique_ptr<Object2>> vo2;
        vo2.push_back(make_unique<Object2>());
        bla2(vo2.begin(), vo2.end());
    }
    

    Disclaimer: kenne mich mit Binärgrezen nicht aus.



  • Moin,

    danke erstmal für deine Antwort !

    wob schrieb:

    fritz-cola schrieb:

    Also ich habe in einem shared object eine methode die in etwa diese signatur hat
    void bla(Object*, int numberOfObjects, int sizeOfObject);
    So kann die shared lib mit allen objecten des structs aus V1 umgehen (somit halt auch mit allen nachfolgenden).

    Das würde ich bezweifeln wollen, da Object und Object2 unterschiedlich groß sind. Du musst also schon in dem Bla-Code wissen, wie groß die sind. Wenn, dann bräuchtest du also ein Array auf Pointer, also Object**.

    Ne das funktioniert schon.. es ist halt mega hässlich. Es funktioniert weil bla weiß das sich in Object i befindet.. also kann bla mit i interagieren.
    Auch in V2 des structs befindet sich I an der stelle wie V1 so dass es da auch keine probleme gibt. Ist halt mega hässlich irgendwie :p

    Also bla sieht ja wie folgt aus:

    bla(Object* obj, int numberOfObjects, int sizeOfObject) {
      int size = numberOfObjects * sizeOfObject;
      for (unsigned indexInfo= 0; indexInfo< numberOfObjects; ++indexInfo) {
        Object *currentObj = reinterpret_cast<Object*>(reinterpret_cast<char *>(obj) + indexInfo * sizeOfObject);
    
        // do something with i from V1
      }
    }
    

    Und da das so mega hässlich und hacky ist, würde ich gerne etwas anderes benutzen... aber ich weiß halt echt nicht was man da machen kann



  • wenns nur darum geht das is c++maessig aussieht, koenntest es z.b. so machen:

    // bissl helper code:
    struct array_config
    {
       array_config(base_object *t_arr, int t_count, int t_diff) : base_addr(t_arr), count(t_count), diff(t_diff) { }
    
       base_object *base_addr;
       int count;
       int diff;
    };
    
    template<class T>
    inline auto config_array(T *arr, int t_count) -> array_config
    {
       return array_config(static_cast<base_object*>(arr), t_count, sizeof(T));
    }
    
    struct my_iter
    {
       my_iter(const my_iter &other) noexcept : addr(other.addr), diff(other.diff) { }
       my_iter(my_iter &&other) noexcept : addr(other.addr), diff(other.diff) { other.addr = nullptr; }
       my_iter(const array_config &conf, bool is_end_it = false) : addr(conf.base_addr), diff(conf.diff) 
       { if(is_end_it == true) *reinterpret_cast<char**>(&addr) += diff * conf.count; }
    
       auto operator=(const my_iter &other) noexcept -> my_iter& { addr = other.addr; diff = other.diff; return *this; }
       auto operator=(my_iter &&other) noexcept -> my_iter& { addr = other.addr; diff = other.diff; other.addr = nullptr; return *this; }
    
       auto operator*(void) const noexcept -> const base_object& { return *addr; }
       auto operator*(void) noexcept -> base_object& { return *addr; }
       auto operator->(void) const noexcept -> const base_object* { return addr; }
       auto operator->(void) noexcept -> base_object* { return addr; }
    
       auto operator++(void) noexcept -> my_iter& 
       { 
          *reinterpret_cast<char**>(&addr) += diff; 
          return *this; 
       }
    
       auto operator++(int) noexcept -> my_iter { my_iter temp(*this); ++(*this); return temp; }
    
       base_object *addr;
       int diff;
    };
    
    auto operator==(const my_iter &lhv, const my_iter &rhv) noexcept -> bool { return lhv.addr == rhv.addr; }
    auto operator!=(const my_iter &lhv, const my_iter &rhv) noexcept -> bool { return lhv.addr != rhv.addr; }
    auto operator<(const my_iter &lhv, const my_iter &rhv) noexcept -> bool { return lhv.addr < rhv.addr; }
    
    auto begin(const array_config &conf) noexcept -> my_iter
    {
       return my_iter(conf);
    }
    
    auto end(const array_config &conf) noexcept -> my_iter
    {
       return my_iter(conf, true);
    }
    
    /* -------------------------------------------------------------------------- */
    // jetzt deine klassen:
    struct base_object
    {
       base_object(void) : i(0) { }
       base_object(int ii) : i(ii) { }
       int i;
    };
    
    struct object : base_object
    {
       object(void) : j(0) { }
       object(int jj) : base_object(jj + 1), j(jj) { }
       int j;
    };
    
    // nun die bla funktion, die mit dem array arbeiten soll:
    auto bla(const array_config &conf) -> void
    {
       for(auto const &elem : conf)
       {
          std::cout << elem.i << " ";
       }
    
       std::cout << std::endl;
    }
    
    // nun der aufruf von bla:
    object obj[10];
    
       for(int i = 0;i < 10; ++i)
       {
          obj[i].i = 12 + i;
          obj[i].j = 100 + i;
       }
    
       bla(config_array(obj, 10));
    

    Ergebnis:

    12 13 14 15 16 17 18 19 20 21
    

    Meep Meep


  • Mod

    Meep Meep schrieb:

    wenns nur darum geht das is c++maessig aussieht, koenntest es z.b. so machen:

    *reinterpret_cast<char**>(&addr) += diff;
    

    Und wieso mit UB? Ist es wirklich so schwer, sich auf einen Zeigertyp festzulegen, and dass Ganze dann ohne verbotenes Aliasing durchzuziehen?



  • auf welchen zeigertyp wuerdest du dich da festlegen ?


  • Mod

    Meep Meep schrieb:

    auf welchen zeigertyp wuerdest du dich da festlegen ?

    base_object*, char* oder void* bieten sich an. Persönlich würde ich auch base_object* verwenden, aber konsequent:
    wenn es ein base_object* ist, darf es nicht als char* angefasst werden.



  • camper schrieb:

    Meep Meep schrieb:

    auf welchen zeigertyp wuerdest du dich da festlegen ?

    base_object*, char* oder void* bieten sich an. Persönlich würde ich auch base_object* verwenden, aber konsequent:
    wenn es ein base_object* ist, darf es nicht als char* angefasst werden.

    ich caste ja nur nach char* um aufs byte genau den zeiger zu verschieben. schliesselich kann der versatz auch != eines vielfachen von sizeof(base_object) sein. ich war der meinung, das wenn es sich um pod-strukturen handelt, man das dann problemlos machen kann, wenn man den versatz richtig berechnet.
    sry die frage aber warum ist das dann UB ?

    Meep Meep


  • Mod

    Meep Meep schrieb:

    sry die frage aber warum ist das dann UB ?

    Weil es die Aliasing-Regeln verletzt: du behandelst das Object addr, als ob es ein Objekt des Typs char* wäre.
    Das wäre in Ordnung:

    addr = reinterpret_cast<base_object*>(reinterpret_cast<char*>(addr) + diff);
    


  • camper schrieb:

    Meep Meep schrieb:

    sry die frage aber warum ist das dann UB ?

    Weil es die Aliasing-Regeln verletzt: du behandelst das Object addr, als ob es ein Objekt des Typs char* wäre.
    Das wäre in Ordnung:

    addr = reinterpret_cast<base_object*>(reinterpret_cast<char*>(addr) + diff);
    

    danke fuer die info zur aliasing regel. kannte die noch nicht.
    hab mir gerade nochmal ueberlegt wie es dazu kam das ich die zeile so geschrieben hab.
    urspruenglich hab ich geschrieben:

    reinterpret_cast<char*>(addr) += diff;
    

    da bekam ich dann aber den fehler:

    error C2106: "+=": Linker Operand muss ein L-Wert sein
    

    aus dem grund hab ich das dann in

    *reinterpret_cast<char**>(&addr) += diff;
    

    geaendert, weil ich ehrlich gesagt zu faul war um nach dem fehler zu suchen.

    Meep Meep


Anmelden zum Antworten