2009-02-11 3 views
23

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

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

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

pthread_mutex_t my_mutex; 

иметь тот же эффект, как

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER; 

ответ

32

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

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

ужасный псевдо-код с не проверяя никакой ошибки на таНос или бесплатно

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.

+3

О, сэр, как мы скучаем по тебе. – joeforker

+5

... как ужасная горящая сыпь. – Randolpho

+0

И как cfront обрабатывал конструктор в начале? – claf

8

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

4

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

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

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

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 для определения интерфейса в файл системных драйверов. Это стиль письма, который может понравиться или может не понравиться. Мне это не очень нравится, потому что я продолжаю использовать элементы структур для данных, а не для эмуляции вызовов функций-членов, которые выглядят на популярных объектно-ориентированных языках.

+0

Надеюсь, ваш компилятор достаточно умен, чтобы избежать копирования всей структуры в операторе return. – joeforker

+2

Как неявно указывает joeforker, лучше использовать указатель, чем оператор возврата. Что делать, если ваша структура особенно велика? Большинство компиляторов скопируют возвращаемое значение. – Randolpho

+0

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

0

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

+0

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

+0

-2 кажется суровым для чего-то совершенно правильного. +1 для правосудия. Вы можете добавить указатель на функцию «конструктор», но вам все равно придется указывать глобальную функцию перед ее вызовом, так что вы можете также вызвать глобальную функцию напрямую! Кроме того, вы не получите «это». – MrZebra

+0

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

1

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

+0

Можете ли вы разработать свою идею макросов? – claf

+0

когда-либо слышал о заводских функциях? простая функция, которая выделяет и заполняет структуру. нет необходимости в макросах. на самом деле, это именно то, что делают конструкторы C++. – Javier

+0

@javier: да, но только в контексте Java. Я не использовал C с 1994 года и не хочу возвращаться :) – paul

2

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

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

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

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

3

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

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

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» для каждого класса у вас есть, а затем вызвать конструктор, хранящуюся в них. То же самое относится и к деструктор и т.д.

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

#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. МЕТОД будет вызывать «метод» для этого экземпляра. Обратите внимание, что для реального кода вам нужно будет проверить, что inst на самом деле является экземпляром something (сравните указатели на классы, что-то в этом роде).

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

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

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

+0

Когда-либо слышал о GOBJECT ? – joeforker

+0

@joe: Вы когда-нибудь слышали об Amiga? :) –

+0

моя ошибка, мой вопрос касался конструктора по умолчанию, я хотел бы иметь некоторое поле структуры, инициализированное в объявлении, без вызова конструктора. – claf

16

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

например.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++.

1

Возможно, вы захотите взглянуть на компилятор C++. Это дает вам больше объектно-ориентированных функций, чем вы можете запомнить на языке, имеющем синтаксис типа C, более элегантным и стандартным способом, чем попытка создания конструкторов и т. Д. На C с библиотеками и макросами.

Если это не сработает для вас, посмотрите на GObject. http://en.wikipedia.org/wiki/GObject. Это объектная система для C используется в GTK и Gnome, что в значительной степени чудаки «объекты в С» до 11.

Материал из Википедии:

объект системы GLib или GObject, библиотека свободного программного обеспечения (крытая LGPL), который обеспечивает портативную объектную систему и прозрачную межъязыковую совместимость.

Система поддерживает конструкторы, деструкторы, одно наследование, интерфейсы, виртуальные общедоступные и частные методы и т. Д. Это также намного утомительно и сложно, чем делать то же самое на C++. Повеселись!

10

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

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

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

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

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 для всех одинаковой функции размещения и так далее. Вы делаете публичные функции set/get для элементов, которые вы хотите открыть.

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

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

+0

Как это ни парадоксально, как одно из «преимуществ» C++ заключается в предоставлении инкапсуляции данных через защищенные/частные спецификаторы, и все же большинство его шаблонов использования связаны с классами, показывающими их кишки для всех остальных (но не позволяющие любой, чтобы коснуться их, конечно). Можно использовать тот же подход, что и в C, но это чаще всего не обескураживает, потому что это «не ООП», а подход «пимпл», который по моему опыту вводит только служебные и шаблонные коды. – Tomis

2

Вот немного макро магия вокруг 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

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

Это делается на языке 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(); 
1

парования является простой программой, которая делает использование конструктора. Функция «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; 
} 

Надежда это поможет вам.

Смежные вопросы