2013-05-18 4 views
2


Я пытаюсь воссоздать некоторые классы из стандартной библиотеки C++ в C. Например, класс std :: pair.
Чтобы эмулировать шаблоны, я, конечно, использовал макросы. Вот пример того, как это выглядит:Шаблоны из C++ в C

#define _TEMPLATE_PAIR_STRUCT(T1, T2, TNAME, STRNAME) \ 
typedef struct {         \ 
    T1* first;         \ 
    T2* second;         \ 
} STRNAME; 

#define _TEMPLATE_PAIR_NEW(T1, T2, TNAME, STRNAME) \ 
    STRNAME* TNAME##_new()       \ 
    {            \ 
     STRNAME *new = malloc(sizeof(STRNAME)); \ 
     new->first = malloc(sizeof(T1));   \ 
     new->second = malloc(sizeof(T2));   \ 
     return new;        \ 
    } 

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

Есть ли способ исправить это, поэтому я могу использовать эти «шаблоны» в C?

+6

Вы когда-нибудь слышали о '#ifndef #define' и включают gaurd: http://en.wikipedia.org/wiki/Include_guard? – 0x90

+18

Почему бы просто не программировать на C++? – Sebivor

+1

Ха, я видел такой метод в некоторых правильных проектах на С. Мех, почему бы и нет. Вы просто должны быть осторожны, чтобы правильно разобраться. Вам, вероятно, потребуется несколько файлов. –

ответ

5

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

Мне не очень нравится это решение, но вот оно.

Один заголовок, чтобы исключить их всех (pair.h)

#ifndef TEMPLATE_PAIR 
#define TEMPLATE_PAIR 

#include <stdlib.h> 

#define PAIR_NAME(T1, T2) T1##T2##NAME 
#define PAIR_PTR_NAME(T1, T2) T1##T2##PTR_NAME 

#define PAIR_CREATE(T1, T2) MAKE##T1##T2 
#define PAIR_PTR_CREATE(T1, T2) MAKE_PTR##T1##T2 

#define PAIR_PTR_FREE(T1, T2) FREE##T1##T2 

#define PAIR_DEFINITION(T1, T2) \ 
    typedef struct { \ 
    T1 first; \ 
    T2 second ; \ 
    } PAIR_NAME(T1, T2) 

#define PAIR_PTR_DEFINITION(T1, T2) \ 
    typedef struct { \ 
    T1* first; \ 
    T2* second ; \ 
    } PAIR_PTR_NAME(T1, T2) 

#define MAKE_PAIR_DECLARE(T1, T2) PAIR_NAME(T1, T2) PAIR_CREATE(T1, T2) (const T1& V1, const T2& V2) 
#define MAKE_PAIR_PTR_DECLARE(T1, T2) PAIR_PTR_NAME(T1, T2) PAIR_PTR_CREATE(T1, T2) (const T1& V1, const T2& V2) 
#define PAIR_PTR_FREE_DECLARE(T1, T2) void PAIR_PTR_FREE(T1, T2) (PAIR_PTR_NAME(T1, T2) & Pair) 

#define MAKE_PAIR_SIGNATURE(T1, T2) PAIR_NAME(T1, T2) PAIR_CREATE(T1, T2) (const T1& V1, const T2& V2) 
#define MAKE_PAIR_PTR_SIGNATURE(T1, T2) PAIR_PTR_NAME(T1, T2) PAIR_PTR_CREATE(T1, T2) (const T1& V1, const T2& V2) 

#define FREE_PAIR_PTR_SIGNATURE(T1, T2) void PAIR_PTR_FREE(T1, T2) (PAIR_PTR_NAME(T1, T2) & Pair) 

#define MAKE_PAIR_DEFINE(T1, T2) \ 
    MAKE_PAIR_SIGNATURE(T1, T2) { \ 
     PAIR_NAME(T1, T2) pair; \ 
     pair.first = V1; \ 
     pair.second = V2; \ 
     return pair; \ 
     } 

#define MAKE_PAIR_PTR_DEFINE(T1, T2) \ 
    MAKE_PAIR_PTR_SIGNATURE(T1, T2) { \ 
     PAIR_PTR_NAME(T1, T2) pair; \ 
     pair.first = malloc(sizeof(T1)); \ 
     if (pair.first != 0) *(pair.first) = V1; \ 
     pair.second = malloc(sizeof(T2)) ; \ 
     if (pair.second != 0) *(pair.second) = V2; \ 
     return pair; \ 
     } 

#define PAIR_PTR_FREE_DEFINE(T1, T2) \ 
    FREE_PAIR_PTR_SIGNATURE(T1, T2) { \ 
    free(Pair.first); \ 
    free(Pair.second); \ 
    } 

#endif 

Один заголовок, чтобы привести их все (defs.h):

#ifndef DEFS_HEADER 
#define DEFS_HEADER 

#include "pair.h" 

typedef int* pInt; 

PAIR_DEFINITION(int, int); 
PAIR_DEFINITION(int, double); 
PAIR_DEFINITION(double, double); 
PAIR_DEFINITION(pInt, pInt); 
PAIR_DEFINITION(float, int); 

PAIR_PTR_DEFINITION(int, int); 

MAKE_PAIR_DECLARE(int, int); 
MAKE_PAIR_DECLARE(int, double); 
MAKE_PAIR_DECLARE(double, double); 
MAKE_PAIR_DECLARE(pInt, pInt); 
MAKE_PAIR_DECLARE(float, int); 

MAKE_PAIR_PTR_DECLARE(int, int); 
PAIR_PTR_FREE_DECLARE(int, int); 

#endif 

И в темноте связывают их (осущ. в):

#include "defs.h" 

MAKE_PAIR_DEFINE(int, int); 
MAKE_PAIR_DEFINE(int, double); 
MAKE_PAIR_DEFINE(double, double); 
MAKE_PAIR_DEFINE(pInt, pInt); 

// manual "instantiation" 
MAKE_PAIR_SIGNATURE(float, int) 
{ 
    PAIR_NAME(float, int) local; 

    local.first = V1; 
    local.second = V2; 
    return local; 
} 

MAKE_PAIR_PTR_DEFINE(int, int); 
PAIR_PTR_FREE_DEFINE(int, int); 

В земле главной где тени лежат:

#include "defs.h" 


int main(void) 
{ 
    PAIR_NAME(int, int) myPairInts; 
    PAIR_NAME(double, double) myPairDoubles; 
    PAIR_NAME(pInt, pInt) myPairPointers; 
    PAIR_NAME(float, int) myPairOther; 

    PAIR_PTR_NAME(int, int) pairPtr; 


    myPairInts = PAIR_CREATE(int, int) (1, 2); 
    myPairDoubles = PAIR_CREATE(double, double) (5, 6.5); 
    myPairPointers = PAIR_CREATE(pInt, pInt) (0, 0); 
    myPairOther = PAIR_CREATE(float, int) (1, 1); 

    pairPtr = PAIR_PTR_CREATE(int, int) (1, 2); 

    PAIR_PTR_FREE(int, int) (pairPtr); 


    return 0; 
} 

PAIR_NAME создает структуру, содержащую значения, PAIR_PTR_NAME содержит указатели на значения. PAIR_CREATE и PAIR_PTR_CREATE создают значения и заполняют данные внутри пары.

Вам нужно будет определить все необходимые комбинации в «impl.c». Любой блок компиляции может #include "defs.h" и использовать пары.

EDIT - Ответы на вопросы:

  1. «Не будет ли это причиной проблемы, когда я использую этот раз в библиотеке или что-то в этом роде, а затем снова в программе, которая использует эту библиотеку как и пара «шаблон»?»

«pair.h» содержит только макросы, его можно безопасно использовать в любой библиотеке или программе.

Важно, чтобы вы не определяли одну и ту же функцию дважды. Я бы не стал определять одну и ту же структуру дважды.

Вы можете сделать следующее, хотя: - взять pair.h, defs.h и impl.c, как они выше и встраивать их в библиотеку (добавить любые необходимые __declspec(dllexport) и __declspec(dllimport) - вы можете затем #include pair.h и defs.h и используйте пары, определенные там в программе - если вы хотите использовать новые пары, скажем, для (float, float) вам нужно будет добавить новый defs_my_program.h и новый impl_my_program.c, чтобы объявить и определить эти новые пары. Заголовок defs_my_program.h может быть включен вместе с заголовком defs.h, который предоставляет библиотека.

Вы по-прежнему получаете одно объявление и одно определение для каждой пары. Единственным недостатком является то, что вы не можете (безопасно) использовать пары «на лету», они должны быть централизованы.

  1. «, как вы выбрали имена типов и функций, чтобы быть действительно приносят некоторые проблемы с ним. Вы расстались пара с двумя значениями и один с двумя указателями. Кроме того, необходимо будет специализация для пары с одним указатель и одно значение, а для пары с одним значением и одним указателем.Таким образом, я бы уже имел 4 пары пар. Используя это для триплетов или даже более высоких кортежей, мне пришлось бы реализовать 2^n случаев ».

Ну, для начала, вы попросили std::pair, а не для кортежа.

Обратите внимание, что std::pair является эквивалентом PAIR_NAME, нет std::pair, который выделяет динамическую память.

Вам не нужны никакие новые специализации, если вам не нужно автоматически использовать malloc. Пример с pInt показывает, что вы можете создать s pair из (int, int*) или (int*, int). Просто значение указателя должно поступать из-за пределов pair.

Если вы действительно хотите pair из (int, int*), который автоматически выделяет память для int* вам придется добавить его себе , если вы на самом деле нужно. Надеюсь, ты этого не сделаешь.

Для кортежей значений не очень оптимальным решением может быть использование pairpair и еще один элемент. Это даст вам структуру, содержащую три элемента. Что-то вроде этого, вероятно, можно сделать с помощью макромагнетики, сокращая этот экспоненциальный рост, о котором вы беспокоитесь.

+0

Извините, что спросила еще раз, но я хочу убедиться, что правильно понял ваш код: Вы разделили макросы на один для объявления и один для определения. Поэтому я могу использовать макрос для объявления, например, include в исходных файлах и для определения, я добавляю еще один исходный файл. У меня есть два вопроса: –

+0

1. Разве это не вызовет проблемы, когда я буду использовать это один раз в библиотеке или что-то в этом роде, а затем снова в программе, которая использует как эту библиотеку, так и пару «шаблон»? –

+0

2. То, как вы выбрали имена типов и функций, вызывает некоторые проблемы с ним. Вы разделили пару с двумя значениями и один с двумя указателями. Вам также понадобится специализация для пары с одним указателем и одним значением, а для пары с одним значением и одним указателем. Поэтому у меня было бы 4 случая с парами. Используя это для триплетов или даже более высоких кортежей, мне пришлось бы реализовать 2^n случаев. –

2

Объявление структуры с несколькими моментами не является проблемой.

Определение функции многократного времени является проблемой. Если вы ставите функцию static, она становится проблемой один раз для каждого файла. Даже в C++ иногда люди будут явно создавать шаблоны. У вас может быть один файл со всеми новыми функциями.

Если вы хотите автоматическое решение, вам нужно посмотреть, как компиляторы C++ создают шаблоны. Вот один из методов, который использовался много лет назад.

  • компилировать и компоновать код, без использования PAIR_NEW
  • Вы получите неопределенные _new (функции).
  • Запустите сценарий, который генерирует файл C с соответствующими вызовами макроса PAIR_NEW() для определения неопределенных символов
  • скомпилируйте этот новый файл и повторно включите проект, включая новый файл.
+0

+1 за то, что нужно сделать статическую функцию, если макрос применяется к одному типу в нескольких исходных файлах. –

+0

Вы говорите, что комбинация объявления этих функций как статичных и включенных охранников будет хитростью? Или я не понимаю? –

+0

@AndreasT Выполнение функции static просто заставляет проблему выполняться реже. Включить охранников вообще не помогают. Ваш заголовок имеет только #define, и законно делать одно и то же #define несколько раз. –

1

С учетом вопроса, если это хорошая идея, у вас есть несколько ошибок в вашей реализации.

  • не скрывает ; внутри макроса, это полностью против зрительных ожиданий в месте, где это будет использоваться.
  • кроме C++, функция, которая не принимает параметр, должна иметь void внутри ().
  • имена, начинающиеся с подчеркивания и заглавная буква, зарезервированы для реализации C во всех контекстах. Выбрали лучшее обозначение .
  • кроме C++ ваша функция new не инициализирует данные, это тоже плохое соглашение об именах.
  • вы используете идентификатор new в качестве локальной переменной, плохо, если вы хотите интерфейс это в какой-то день C++ один
  • макрос для функции следует разделить на три части: (1) для именования, что соединяет имя функции, (2) определение inline и (3) внешнее «мгновение», которое позволяет вы генерируете символ функции только в одном блоке компиляции.
+0

Вопрос не в том, что это хорошая идея, но как исправить описанную проблему. В любом случае: –

+0

1. Я предполагаю, что вы ссылаетесь на; после определения структуры? Хорошо, я бы согласился с вами, если бы это был макрос, который я бы использовал в качестве команды внутри функции. Однако эти макросы должны использоваться снаружи и, следовательно, не должны требовать; после этого. –

+0

2. Не знал, что есть разница, спасибо. –