Конструктор по умолчанию в C - PullRequest
26 голосов
/ 11 февраля 2009

Есть ли способ иметь какой-то конструктор по умолчанию (например, C ++) для пользовательских типов C, определенных со структурой?

У меня уже есть макрос, который работает как быстрый инициализатор (например, для pthread_mutex), но я хотел бы знать, можете ли вы случайно (некоторые) все поля структуры заполнить при объявлении. *

Например, на примере pthread_mutex я бы хотел

pthread_mutex_t my_mutex;

, чтобы иметь тот же эффект, что и

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;

Ответы [ 13 ]

36 голосов
/ 11 февраля 2009

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

Также функции, которые создают структуру и инициализируют ее (например, фабрику) - поэтому никогда не бывает, чтобы структура была "неинициализирована" в "клиентском" коде. Конечно - это предполагает, что люди следуют соглашению и используют "конструктор" / фабрику ...

ужасный псевдокод без проверки ошибок на malloc или free

somestruct* somestruct_factory(/* per haps some initializer agrs? */)
{
  malloc some stuff
  fill in some stuff
  return pointer to malloced stuff
}


void somestruct_destructor(somestruct*)
{
  do cleanup stuff and also free pointer
  free(somestruct);
}

Кто-то, вероятно, придет и объяснит, как некоторые ранние препроцессоры / компиляторы C ++ работали, чтобы сделать все это в C.

19 голосов
/ 11 февраля 2009

C ++ в этом случае отличается от C тем, что у него нет «классов». Тем не менее, C (как и многие другие языки) все еще может использоваться для объектно-ориентированного программирования. В этом случае ваш конструктор может быть функцией, которая инициализирует структуру. Это то же самое, что конструкторы (только другой синтаксис). Другое отличие состоит в том, что вы должны размещать объект с помощью malloc () (или некоторого варианта). В C ++ вы бы просто использовали оператор 'new'.

например. Код C ++:

class A {
  public:
    A() { a = 0; }
    int a;
};

int main() 
{
  A b;
  A *c = new A;
  return 0;
}

эквивалентный код C:

struct A {
  int a;
};

void init_A_types(struct A* t)
{
   t->a = 0;
}

int main()
{
   struct A b;
   struct A *c = malloc(sizeof(struct A));
   init_A_types(&b);
   init_A_types(c);
   return 0;
}

функция 'init_A_types' функционирует как конструктор в C ++.

11 голосов
/ 11 февраля 2009

Давайте поговорим о полном инженерном решении, которое считалось лучшей практикой в ​​старину.

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

Мы можем это исправить.

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

typedef struct t_ProcessStruct *t_ProcessHandle;

extern t_ProcessHandle NewProcess();
extern void DisposeProcess(t_ProcessHandle handle);

typedef struct t_PermissionsStruct *t_PermissionsHandle;

extern t_PermissionsHandle NewPermissions();
extern void DisposePermissions(t_PermissionsHandle handle);

extern void SetProcessPermissions(t_ProcessHandle proc, t_PermissionsHandle perm);

затем вы создаете частный заголовочный файл, который содержит такие определения:

typedef void (*fDisposeFunction)(void *memoryBlock);

typedef struct {
    fDisposeFunction _dispose;
} t_DisposableStruct;

typedef struct {
    t_DisposableStruct_disposer; /* must be first */
    PID _pid;
    /* etc */
} t_ProcessStruct;

typedef struct {
    t_DisposableStruct_disposer; /* must be first */
    PERM_FLAGS _flags;
    /* etc */
} t_PermissionsStruct;

и тогда в своей реализации вы можете сделать что-то вроде этого:

static void DisposeMallocBlock(void *process) { if (process) free(process); }

static void *NewMallocedDisposer(size_t size)
{
    assert(size > sizeof(t_DisposableStruct);
    t_DisposableStruct *disp = (t_DisposableStruct *)malloc(size);
    if (disp) {
       disp->_dispose = DisposeMallocBlock;
    }
    return disp;
}

static void DisposeUsingDisposer(t_DisposableStruct *ds)
{
    assert(ds);
    ds->_dispose(ds);
}

t_ProcessHandle NewProcess()
{
    t_ProcessHandle proc =  (t_ProcessHandle)NewMallocedDisposer(sizeof(t_ProcessStruct));
    if (proc) {
        proc->PID = NextPID(); /* etc */
    }
    return proc;
}

void DisposeProcess(t_ProcessHandle proc)
{
    DisposeUsingDisposer(&(proc->_disposer));
}

Что происходит, так это то, что вы делаете предварительные объявления для своих структур в ваших публичных заголовочных файлах. Теперь ваши структуры непрозрачны, что означает, что клиенты не могут с ними работать. Затем в полное объявление вы добавляете деструктор в начало каждой структуры, которую вы можете вызывать в общем. Вы можете использовать один и тот же распределитель malloc для каждой и той же функции dispose. Вы делаете общедоступными функции set / get для элементов, которые вы хотите раскрыть.

Внезапно ваш код становится намного более вменяемым. Вы можете получить структуры только от распределителей или функций, которые вызывают распределители, что означает, что вы можете инициализировать узкое место. Вы строите деструкторы, чтобы объект мог быть уничтожен. И вы идете. Кстати, лучшим именем, чем t_DisposableStruct, может быть t_vTableStruct, потому что это именно так. Теперь вы можете создавать виртуальное наследование, используя vTableStruct, который является указателем на функцию. Вы также можете делать вещи, которые вы не можете делать на чистом языке (как правило), например, изменять элементы выбора виртуальной таблицы на лету.

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

8 голосов
/ 11 февраля 2009

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

4 голосов
/ 11 февраля 2009

Вы можете написать функцию, которая возвращает структуру C:

struct file create_file(int i, float f) {
    struct file obj = { i, f };
    // other code here...
    return obj;
}

Если вам интересно, можете ли вы иметь «нормальные» функции-члены в C. Ну, вы можете в некоторой степени. Я предпочитаю стиль объекта как первого аргумента. Вы передаете указатель на свою структуру в качестве первого аргумента. Таким образом, вы можете иметь несколько функций, определяющих интерфейс для ваших объектов:

int file_get_integer(struct file *self) { return self->i; }
float file_get_float(struct file *self) { return self->f; }

Если вы пишете в этом стиле, то в конце вы получите абстрактный тип данных. Я видел парней, имитирующих синтаксис вызова функций-членов, используемых в C ++, с помощью указателей на функции в своей структуре и затем выполняющих:

obj.get_integer(&obj);

Используется ядром linux для определения интерфейса с драйверами файловой системы. Это стиль письма, который может нравиться или не нравиться. Мне это не очень нравится, потому что я продолжаю использовать члены структур для данных, а не для эмуляции вызовов функций-членов, чтобы выглядеть в популярных объектно-ориентированных языках.

3 голосов
/ 11 февраля 2009

В основном C ++ создает списки указателей, которые содержат адреса методов. Этот список называется определением класса (в классе def есть еще некоторые данные, но пока мы их игнорируем).

Распространенным примером использования «классов» в чистом С является определение «структурного класса». Одним из полей структуры является фабричная функция, которая возвращает «экземпляры» класса. Я предлагаю использовать макрос, чтобы скрыть приведение:

typedef struct __class * class;
typedef void (*ctor_ptr)(class);
struct class {
    char * name;
    ctor_ptr ctor;
    ... destructor and other stuff ...
}

#define NEW(clz) ((struct something *)(((struct class *)clz)->ctor(clz)))

Теперь вы можете определить ваши классы, создав структуры типа "struct class" для каждого вашего класса, а затем вызвать сохраненный в них конструктор. То же самое касается деструктора и т. Д.

Если вам нужны методы для ваших экземпляров, вы должны поместить их в структуры классов и сохранить указатель на класс в структуре экземпляра:

#define NEW_SOMETHING() ((struct something *)NEW(&something_definition))
#define METHOD(inst, arg) ((struct something_class *)(((struct something *)inst)->clz)->method(inst, arg))

NEW_SOMETHING создаст новый экземпляр «нечто», используя определение класса, хранящееся в структуре something_definition. METHOD вызовет «метод» в этом случае. Обратите внимание, что для реального кода вы захотите проверить, что inst на самом деле является экземпляром something (сравните указатели классов, что-то вроде этого).

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

Обратите внимание, что вы можете делать все, что вы можете делать в C ++ на чистом C. Компилятор C ++ волшебным образом не заменяет ваш процессор чем-то другим. Для этого требуется гораздо больше кода.

Если вы хотите посмотреть пример, посмотрите glib (часть проекта Gtk +).

2 голосов
/ 17 февраля 2014

Fallowing - простая программа, использующая конструктор. Функция «default_constructor» вызывается внутри main без явного вызова функции.

#include        <stdio.h> 

void __attribute__ ((constructor)) default_constructor() 
{ 
    printf("%s\n", __FUNCTION__); 
} 

int main() 
{ 
    printf("%s\n",__FUNCTION__);
    return 0; 
}

Выход: default_constructor Основной

В функции конструктора вы можете включить некоторые операторы инициализации в функцию конструктора и, наконец, можете сделать освобождение в деструкторе следующим образом:

#include    <stdio.h> 
#include    <stdlib.h>

struct somestruct
{
    int     empid;
    char *  name;
};

struct somestruct * str1;

void __attribute__ ((constructor)) a_constructor() 
{ 
    str1 = (struct somestruct *) malloc (sizeof(struct somestruct));
    str1 -> empid = 30228;
    str1 -> name = "Nandan";
} 

void __attribute__ ((destructor)) a_destructor()
{
    free(str1);
}

int main() 
{ 
    printf("ID = %d\nName = %s\n", str1 -> empid, str1 -> name);
    return 0;
}

Надеюсь, это поможет вам.

2 голосов
/ 11 февраля 2009

Использование функций для создания и удаления структур уже упоминалось. Но в комментарии вы упомянули, что вам нужен «конструктор по умолчанию» - я думаю, вы имеете в виду, что вы хотите инициализировать некоторые (все?) Поля структуры значениями по умолчанию.

Это делается в C с использованием некоторого соглашения о кодировании - либо функций, макросов, либо микса. Я обычно делаю что-то вроде следующего -

struct some_struct {
    int a;
    float b;
};
#define some_struct_DEFAULT { 0, 0.0f}
struct some_struct *some_struct_create(void) {
    struct some_struct *ptr = malloc(sizeof some_struct);
    if(!ptr)
        return ptr;

    *ptr = some_struct_DEFAULT;
    return ptr;
}
// (...)
struct some_struct on_stack = some_struct_DEFAULT;
struct some_struct *on_heap = some_struct_create();
2 голосов
/ 11 февраля 2009

Вот небольшая макромагия вокруг составных литералов malloc(), memcpy() и C99:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define new(TYPE, ...) memdup(&(TYPE){ __VA_ARGS__ }, sizeof(TYPE))

void * memdup(const void * obj, size_t size)
{
    void * copy = malloc(size);
    return copy ? memcpy(copy, obj, size) : NULL;
}

struct point
{
    int x;
    int y;
};

int main()
{
    int * i = new(int, 1);
    struct point * p = new(struct point, 2, 3);

    printf("%i %i %i", *i, p->x, p->y);

    return 0;
}
2 голосов
/ 11 февраля 2009

Предполагая, что вы хотите сделать это в C, значит, ваш вопрос не о структурах в C ++:

Обычно я просто создаю функцию init_whither, которая берет указатель на структуру (или другую переменную) и устанавливает для нее нужные мне значения.

Если это глобальная переменная (которая инициализируется нулем), вы могли бы имитировать конструктор без аргументов, имея в нем флаг "initialized", а затем позволяйте другим функциям проверять, что флаг и инициализировать структуру, если флаг равен нулю. Я совсем не уверен, что это хорошая идея.

И, как заметил кто-то другой, с макросами можно сделать что-то ужасное ...

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