Объединение общего API для похожих, но разных структур данных в C

Вот код для чтения матрицы, обратите внимание, что у вас также есть функция csvwrite в matlab

void loadFromCSV( const std::string& filename )
{
    std::ifstream       file( filename.c_str() );
    std::vector< std::vector<std::string> >   matrix;
    std::vector<std::string>   row;
    std::string                line;
    std::string                cell;

    while( file )
    {
        std::getline(file,line);
        std::stringstream lineStream(line);
        row.clear();

        while( std::getline( lineStream, cell, ',' ) )
            row.push_back( cell );

        if( !row.empty() )
            matrix.push_back( row );
    }

    for( int i=0; i<int(matrix.size()); i++ )
    {
        for( int j=0; j<int(matrix[i].size()); j++ )
            std::cout << matrix[i][j] << " ";

        std::cout << std::endl;
    }
}
-1
задан Richard Chambers 16 January 2019 в 18:58
поделиться

5 ответов

C11 поддерживает типовые выражения, которые должны делать то, что вы ищете:

#define Set_b(X, y) _Generic((X), A: Set_b_A,\
                                  B: Set_b_B \
                            )((X), (y))

Затем просто реализовать Set_b_A и Set_b_B для соответствующих типов.

0
ответ дан Carl Norum 16 January 2019 в 18:58
поделиться

Прежде всего, рекомендуется передавать указатель на структуру, а не полную копию по значению.

Метод 1: Существует один метод, который широко используется в программировании ядра Linux, и это container_of макрос. Если вы дадите ему указатель на элемент в качестве входных данных, он даст вам структуру, содержащую этот элемент. Вы можете сделать нечто подобное внутри функций set_b(), set_c(), set_d().

Метод 2: На примере set_b() можно добавить еще 1 дополнительный аргумент, чтобы рассказать о типе структуры, а также сделать первый указатель void *. Подпись станет:

set_b(void * str, int num, int str_type)

Вы можете использовать str_type как 1 для struct A и 0 для struct B. Теперь внутри определения функции, вы должны проверить тип и снова переустановить указатель void на правильный тип struct

0
ответ дан Syed.Waris 16 January 2019 в 18:58
поделиться

Я бы сделал это так. Он отлаживаем и не генерирует слишком много кода (memcpy будет оптимизирован из реального кода на любом уровне оптимизации) https://godbolt.org/z/lMShik

Кстати в этом случае небезопасная версия IMO так же безопасна, как и IMO, она нарушает строгие правила псевдонимов.

typedef enum
{
    TYPE_A,
    TYPE_B,
    TYPE_C
}Types;


struct a 
{
    int a;
    int b;
    int c;
    int d;    
};

struct b 
{
    int a;
    int b;
    int c;
};

struct c 
{
    int a;
    int b;
};

void inline __attribute__((always_inline)) Set_a(void *ptr, Types type, int value)
{
    struct a a;
    struct b b;
    struct c c;

    switch(type)
    {
        case TYPE_A:
            memcpy(&a, ptr, sizeof(a));
            a.a = value;
            memcpy(ptr, &a, sizeof(a));
            break;
        case TYPE_B:
            memcpy(&b, ptr, sizeof(b));
            b.a = value;
            memcpy(ptr, &b, sizeof(b));
            break;
        case TYPE_C:
            memcpy(&c, ptr, sizeof(c));
            c.a = value;
            memcpy(ptr, &c, sizeof(c));
            break;
    }
}


void inline __attribute__((always_inline)) Set_a_unsafe(void *ptr, Types type, int value)
{
    struct a a;
    struct b b;
    struct c c;

    switch(type)
    {
        case TYPE_A:
            ((struct a *)ptr) -> a = value;
            break;
        case TYPE_B:
            ((struct b *)ptr) -> a = value;
            break;
        case TYPE_C:
            ((struct c *)ptr) -> a = value;
            break;
    }
}

    struct a x,y;


int main()
{
    Set_a(&x, TYPE_A, 45);
    Set_a_unsafe(&y, TYPE_B, 100);

    printf("%d\n", x.a);
    printf("%d\n", y.a);
}
0
ответ дан P__J__ 16 January 2019 в 18:58
поделиться

Существует несколько различных альтернативных подходов, которые могут быть использованы, однако это зависит от того, чем вы владеете, и может отличаться от того, чем вы не владеете, и не может изменять или изменять. Реальная реализация может зависеть от того, как часто structA используется с API по сравнению с structB. Вы действительно не предоставляете достаточно информации для окончательного предлагаемого подхода.

Похоже, что ваша публикация может быть изменена следующим образом.

Есть две структуры, structA и structB, которые имеют общих членов. Эти общие члены хранят тот же тип данных, и при проверке вся structB содержится в structA, как в:

typedef struct {
    int a;
    int b;      // beginning of portion that is same as structB below.
    int c;
    int d;      // end of portion that is same as structB below.
    int e;
} structA;

typedef struct {
    int b;       // same type of data as in b member of structA above
    int c;       // same type of data as in c member of structA above
    int d;       // same type of data as in d member of structA above
} structB;

Для конкретного примера, structA описывает объект в некотором трехмерном пространство, местоположение которого является кортежем x, y, z, который указан в structA членах b, c, d и structB, используется для хранения местоположения только как x, y, z кортеж. [одна тысяча сто восемьдесят-два]

У вас есть API, который работает с данными в structB, и, поскольку те же данные находятся в structA, вы столкнулись с проблемой, что у вас должен быть API, состоящий из набора функций, который дублируется, один версия API, которая принимает в качестве аргумента structB, а другая принимает в качестве аргумента structA.

Чтобы расширить наш конкретный пример объектов в трехмерном пространстве, у вас есть API, который содержит функцию translate(), которая переводит координаты на некоторое расстояние. Поскольку у вас есть две разные структуры в соответствии с MISRA C, вам понадобятся две разные версии этой функции: translate_structA(), которая принимает в качестве аргумента a structA, и вторую translate_structB(), которая принимает в качестве аргумента a structB. ].

Итак, вы столкнулись с необходимостью написать две версии каждой функции в вашем API, и вы не хотите этого делать.

Альтернатива 1 - заменить клонированные элементы фактической структурой

Использовать хорошую разработку программного обеспечения и вместо использования этого типа данных structB в structA в качестве клонированного набора элементов, вместо этого замените этих клонированных членов на structB.

typedef struct {
    int b;       // same type of data as in b member of structA above
    int c;       // same type of data as in c member of structA above
    int d;       // same type of data as in d member of structA above
} structB;

typedef struct {
    int a;
    structB xyz;    // replace the cloned members with an actual structB
    int e;
} structA;

Затем вы пишете API, который работает с structB только в терминах structB. В тех местах, где вы используете structA, вы просто используете член xyz в интерфейсе вызова функции.

Хорошая особенность этого подхода заключается в том, что при добавлении дополнительных новых типов данных, требующих structB, вы просто добавляете элемент structB вместо клонирования элементов, и API, использующий structB, может быть используется с новыми типами данных.

Однако, чтобы воспользоваться этим подходом, вам необходимо владеть технологией и иметь возможность вносить подобные изменения. С другой стороны, это самая простая, простая и читаемая альтернатива из всех, которые я могу себе представить. Он также должен иметь неплохую эффективность во время выполнения.

Примечание о следующих двух альтернативах

Прежде чем перейти к следующим двум альтернативам, вы должны рассмотреть основной недостаток в обоих из них.

Если зависимость structA от structB не указана как вид контракта с использованием structB в structA, вы вводите своего рода логическое или когнитивное межмодульное соединение , в котором у вас есть общий компонент, который представляет собой сам исходный код, а не программный компонент, полученный из исходного кода.

Техническое обслуживание становится проблемой, потому что теперь две структуры должны быть изменены вместе. И если связь между этими двумя областями не задокументирована в исходном коде и самих определениях структуры, программист, плохо знакомый с кодом, вероятно, упустит это.

И если будут введены новые типы данных, использующие данные structB, шаг клонирования необходимо будет сделать снова, и вы просто расширяете поверхность сложных связей.

Альтернатива 2 - сортировка в / из интерфейсного объекта

Если у вас нет контроля над структурами, то другой альтернативой является выполнение сортировки данных в structA и из него в a structB, а затем написать API только в терминах structB. Тогда в любом месте, где structA нужно использовать API, вы должны иметь маршаллинг или преобразование, в котором выбираются конкретные данные в structA для создания временного structB, который затем используется с функцией. Если функция изменяет данные в structB, вам нужно будет скопировать данные из structB обратно в structA, прежде чем удалять временные.

В качестве альтернативы вы можете решить использовать API в терминах structA с маршаллингом в тех случаях, когда вы хотите использовать structB с API. Эта альтернатива может быть предпочтительнее, если большая часть API использует structA, а лишь немногие используют structB.

Есть несколько способов сделать этот подход маршаллинга, в основном определяемый тем, будут ли интерфейсы API возвращать измененный объект данных или нет.

Во-первых, должен иметь дублированный набор функций, которые вызываются с использованием structA, и этот дублирующий набор функций обрабатывает маршалинг данных между временным structB, который затем используется при вызове фактического API, который занимает structB.

Так что-то вроде:

int funcThing (structB thing);

int funcThing_structA (structA thing) {
    structB temp = {0};
    temp.b = thing.b;
    temp.c = thing.c;
    temp.d = thing.d;
    return funcThing (temp);
}

Альтернативой вышеперечисленному может быть что-то вроде:

int funcThing1 (structB thing);
int funcThing2 (structB thing);
int funcThing3 (structB thing);

int funcThingSet_structA (structA thing, int (*f)(structB thing)) {
    structB temp = {0};
    temp.b = thing.b;
    temp.c = thing.c;
    temp.d = thing.d;
    return f (temp);
}

// and the above is used like
structA thingA;
//  …  code
i = funcThingSet_structA (thingA, funcThing1);  // call funcThing1() with the structA data
i = funcThingSet_structA (thingA, funcThing2);  // call funcThing2() with the structA data

i = funcThingSet_structA (thingA, funcThing3);  // call funcThing3() with the structA data

Если функции могут изменять данные, то вам нужно убедиться, что structA обновляется как в:

int funcThing1 (structB *thing);
int funcThing2 (structB *thing);
int funcThing3 (structB *thing);

int funcThingSet_structA (structA *thing, int (*f)(structB *thing)) {
    structB temp = {0};
    int iRetVal = 0;

    temp.b = thing->b;
    temp.c = thing->c;
    temp.d = thing->d;

    iRetVal = f (&temp);

    thing->b = temp.b;
    thing->c = temp.c;
    thing->d = temp.d;
    return iRetVal;
}

// and the above is used like
structA thingA;
//  …  code
i = funcThingSet_structA (&thingA, funcThing1);  // call funcThing1() with the structA data
i = funcThingSet_structA (&thingA, funcThing2);  // call funcThing2() with the structA data

i = funcThingSet_structA (&thingA, funcThing3);  // call funcThing3() with the structA data

Вы также можете иметь API в терминах structB и использовать вспомогательные функции интерфейса, такие как:

structB *AssignAtoB (structB *pB, structA A) {
    pB->b = A.b;
    pB->c = A.c;
    pB->d = A.d;
    return pB;
}

structB ConvertAtoB (structA A) {
    structB B = {0};
    B.b = A.b;
    B.c = A.c;
    B.d = A.d;
    return B;
}

void AssignBtoA (structA *pA, structB B) {
    pA->b = B.b;
    pA->c = B.c;
    pA->d = B.d;
}

Тогда вы можете сделать что-то вроде:

int funcThing1 (structB thing);
int funcThing2 (structB thing);
int funcThing3 (structB thing);


structA aThing;
//  …. code
{  // create a local scope for this temporary bThing.
    structB bThing = ConvertAtoB (aThing);
    i = funcThing1(bThing);
    // other operations on bThing and then finally.
    AssignBtoA (&aThing, bThing);
}

Или ваши функции API могут возвращать structB, в этом случае вы можете сделать что-то вроде:

structB funcThing1 (structB thing);
structB funcThing2 (structB thing);
structB funcThing3 (structB thing);

structA aThing;
//  …. code
{  // create a local scope for this temporary bThing.
    structB bThing = ConvertAtoB (aThing);
    bThing = funcThing1(bThing);
    bThing = funcThing2(bThing);
    AssignBtoA (&aThing, bThing);
}

или

{  // create a local scope for this temporary bThing.
    structB bThing = ConvertAtoB (aThing);
    AssignBtoA (&aThing, funcThing2(funcThing1(bThing)));
}

или даже просто

AssignBtoA (&aThing, funcThing2(funcThing1(ConvertAtoB (aThing))))

Альтернатива 3 - хрупкое использование указателей

Другой альтернативой является создание указателя, адрес которого начинается с части structB в [ 1172]. Хотя я только смутно знаком с MISRA, я почти не сомневаюсь, что этот подход противоречит правилам, поскольку он в значительной степени мерзок. Тем не менее, здесь все равно, как я видел, это было сделано в старом коде, написанном людьми без надлежащей подготовки специалистов по разработке программного обеспечения.

Используя две вышеупомянутые структуры, создайте вспомогательную функцию или макрос, который сгенерирует указатель на смещение в structA, где начинаются данные structB. Например:

structB MakeClone (structA thing) {
    return *(structB *)&thing.b;   // return a copy of the structB part of structA
}

или

structB *MakePointer (structA *pThing) {
    return (structB *)&thing.b;    // return a pointer to the structB part of structA
}

Макрос препроцессора также может использоваться для генерации указателя второго случая, как в:

#define MAKEPOINTER(pThing) ((structB *)&((pThing)->b))

I также видели, где вместо использования вспомогательной функции с присваиванием, как в:

int funcThing (structB *pBthing);

//  then in code want to use the above function with a structA
structA  aThing = {0};

// do things with aThing then call our function that wants a structB

funcThing (MAKEPOINTER(&aThing));

вместо этого они просто жестко закодируют указатель, в результате чего действительно трудно найти, где это было сделано во время обслуживания: [ 11115]

funcThing ((structB *)&(aThing.b));

Я также видел подход указателя, используемый с присваиванием, используя memcpy() для выполнения присваивания. Поэтому, если у нас есть код, подобный следующему:

structA aThing = {0};
structB bThing = {0};

// somewhere in code we have
memcpy (&bThing, &aThing.b, sizeof(structB));  // assign the structB part of aThing to a structB
// more code to modify bThing then call our function
funcThing (&bThing);
memcpy (&aThing.b, &bThing, sizeof(structB));  // assign the structB back into the structB part of aThing

Использование подхода с указателем хрупко, потому что, если компоновка structA или компоновка structB должна измениться, вещи, вероятно, сломаются. Хуже всего то, что они могут сломаться без указания причины и основной причины.

0
ответ дан Richard Chambers 16 January 2019 в 18:58
поделиться

Вы можете создать интерфейс. Например,

struct A {};                          // As you have defined                                                               
void set_b_for_A(struct A, int) {}    // function that works with A                                          

// interface                                                                          
struct helper {                                                                 
    void *ptr;                        // pointer to your structs variants (A, ...)                                                         
    void (*set_b)(struct helper *, int);   // helper to dispatch to correct worker                                          
};                                                                              


void set_b_helper_for_A(struct helper *pointer, int i) {   // helper for worker A                            
    struct A *s = (struct A *) pointer->ptr;                                    
    set_b_for_A(*s, i);                                                         
} 

struct helper helper_A {/* A struct */, set_b_helper_for_A};

Теперь ваш API

void set_b(struct helper *ptr, int i) {
     ptr->set_b(ptr, i);
}

и вы звоните, например:

set_b(&helper_A, 0);

Сделайте то же самое для других структур

0
ответ дан Amadeus 16 January 2019 в 18:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: