问题是基于一种设计模式解决方案,在其他语言中很容易实现,但很难在C中实现。缩小的代码如下。
在this answer上构建,我正在尝试在匿名函数中找到动态生成的值的解决方案。
摘自答案:
int (*max)(int, int) =
({
int __fn__ (int x, int y) { return x > y ? x : y; }
__fn__;
});
静态图书馆代码
struct Super{
}
void add(struct Super *(*superRef)()) {
// cache the reference (in some linked list)
// later at some point when an event occurs.
struct Super *super = superRef(); // instantiate and use it.
}
链接的客户代码:图书馆代码的用户
struct Sub{
struct Super *super;
}
add(({
struct Sub __fn__() { return malloc(sizeof(struct Sub)); } // error
__fn__;
}));
错误:
error: passing 'void' to parameter of incompatible type 'struct Sub *(*)()
根据澄清请求,请考虑接收对结构对象(未实例化)的引用的静态库文件中的接收函数。 lib从客户端代码接收此对象。
其次,客户端或静态库库不会立即实例化收到的结构引用。稍后当系统中有通知时,将调用结构引用来实例化并执行其余的东西。
我再说一遍,具体要求是保存对库用户传递的结构的非实例化引用(客户端代码)。
基本上是一个Runner,它接收指向一个多态工厂方法的指针,该方法被缓存并稍后调用实例化并在事件发生时执行。
答案 0 :(得分:1)
问题中显示的代码不是标准C,而是GCC支持的GNU C变体。不幸的是,似乎没有gnu-c标记来正确指定所涉及的C的变体。
此外,用例似乎依赖于将特定类型的面向对象范例转换为C库接口。这太可怕了,因为它涉及C根本没有的假设和特征。 C(和GNU-C),C ++和Objective-C是不同的编程语言是有原因的。
"函数的简单答案是返回动态分配的值" ,其中值的类型对库是不透明的,是使用void *
,对于函数指针,(void *)()
。请注意,在POSIX C中,void *
也可以保存函数指针。
更复杂的答案将描述像GObject这样的库如何在C中支持面向对象的范例。
在实践中,特别是在POSIX C中,使用类型标记(通常是int
,但可以是任何其他类型)和联合,可以实现多态结构,基于结构的并集,所有具有该结构type tag作为相同的第一个元素。此类功能的最常见示例是struct sockaddr
。
基本上,您的头文件定义了一个或多个具有相同初始成员的结构,例如
enum {
MYOBJECT_TYPE_DOUBLE,
MYOBJECT_TYPE_VOID_FUNCTION,
};
struct myobject_double {
int type; /* MYOBJECT_TYPE_DOUBLE */
double value;
};
struct myobject_void_function {
int type; /* MYOBJECT_TYPE_VOID_FUNCTION */
void (*value)();
};
最后是所有结构类型的联合类型或具有匿名联合的结构类型(由C11或GNU-C提供),
struct myobject {
union {
struct { int type; }; /* for direct 'type' member access */
struct myobject_double as_double;
struct myobject_void_function as_void_function;
};
};
请注意,从技术上讲,只要该联合可见,将任何结构类型的任何指针强制转换为其他结构类型并访问type
成员是有效的(参见C11 6.5.2.3p6) 。根本不需要 来使用union,它足以定义和显示联合。
尽管如此,为了便于维护(并避免与语言律师崇拜者在C标准中没有阅读该段落的争论),我建议使用包含匿名联盟的结构作为" base"输入库界面。
例如,库可能提供一个函数来返回某个对象的实际大小:
size_t myobject_size(struct myobject *obj)
{
if (obj)
switch (obj->type) {
case MYOBJECT_TYPE_DOUBLE: return sizeof (struct myobject_double);
case MYOBJECT_TYPE_VOID_FUNCTION: return sizeof (struct myobject_void_function);
}
errno = EINVAL;
return 0;
}
在我看来,OP正在尝试实现factory pattern,其中库函数为创建的对象提供规范(OOP中的类),以及生成这些对象的方法后面。
C实现动态类型的唯一方法是通过我在上面显示的那种多态性。这意味着未来对象的规范(同样,OOP中的 class )必须是普通对象本身。
工厂模式本身很容易在标准C中实现。库头文件包含例如
#include <stdlib.h>
/*
* Generic, application-visible stuff
*/
struct any_factory {
/* Function to create an object */
void *(*produce)(struct any_factory *);
/* Function to discard this factory */
void (*retire)(struct any_factory *);
/* Flexible array member; the actual
size of this structure varies. */
unsigned long payload[];
};
static inline void *factory_produce(struct any_factory *factory)
{
if (factory && factory->produce)
return factory->produce(factory);
/* C has no exceptions, but does have thread-local 'errno'.
The error codes do vary from system to system. */
errno = EINVAL;
return NULL;
}
static inline void factory_retire(struct any_factory *factory)
{
if (factory) {
if (factory->retire) {
factory->retire(factory);
} else {
/* Optional: Poison function pointers, to easily
detect use-after-free bugs. */
factory->produce = NULL;
factory->retire = NULL; /* Already NULL, too. */
/* Free the factory object. */
free(factory);
}
}
}
/*
* Library function.
*
* This one takes a pointer and size in chars, and returns
* a factory object that produces dynamically allocated
* copies of the data.
*/
struct any_factory *mem_factory(const void *, const size_t);
其中factory_produce()
是一个辅助函数,它调用工厂生成一个对象,factory_retire()
退出(丢弃/释放)工厂本身。除了额外的错误检查,factory_produce(factory)
相当于(factory)->produce(factory)
,factory_retire(factory)
相当于(factory)->retire(factory)
。
mem_factory(ptr, len)
函数是库提供的工厂函数的示例。它创建了一个工厂,生成动态分配的mem_factory()
调用时看到的数据副本。
库实现本身就是
#include <stdlib.h>
#include <string.h>
#include <errno.h>
struct mem_factory {
void *(*produce)(struct any_factory *);
void (*retire)(struct any_factory *);
size_t size;
unsigned char data[];
};
/* The visibility of this union ensures the initial sequences
in the structures are compatible; see C11 6.5.2.3p6.
Essentially, this causes the casts between these structure
types, for accessing their initial common members, valid. */
union factory_union {
struct any_factory any;
struct mem_factory mem;
};
static void *mem_producer(struct any_factory *any)
{
if (any) {
struct mem_factory *mem = (struct mem_factory *)any;
/* We return a dynamically allocated copy of the data,
padded with 8 to 15 zeros.. for no reason. */
const size_t size = (mem->size | 7) + 9;
char *result;
result = malloc(size);
if (!result) {
errno = ENOMEM;
return NULL;
}
/* Clear the padding. */
memset(result + size - 16, 0, 16);
/* Copy the data, if any. */
if (mem->size)
memcpy(result, mem->data, size);
/* Done. */
return result;
}
errno = EINVAL;
return NULL;
}
static void mem_retirer(struct any_factory *any)
{
if (any) {
struct mem_factory *mem = (struct mem_factory *)any;
mem->produce = NULL;
mem->retire = NULL;
mem->size = 0;
free(mem);
}
}
/* The only exported function:
*/
struct any_factory *mem_factory(const void *src, const size_t len)
{
struct mem_factory *mem;
if (len && !src) {
errno = EINVAL;
return NULL;
}
mem = malloc(len + sizeof (struct mem_factory));
if (!mem) {
errno = ENOMEM;
return NULL;
}
mem->produce = mem_producer;
mem->retire = mem_retirer;
mem->size = len;
if (len > 0)
memcpy(mem->data, src, len);
return (struct any_factory *)mem;
}
基本上,struct any_factory
类型实际上是多态的(不在应用程序中,而只在库中)。它的所有变体(struct mem_factory
)都有两个共同的初始函数指针。
现在,如果我们检查上面的代码,并考虑工厂模式,你应该意识到函数指针提供的价值非常小:你可以使用我在前面的答案中展示的多态类型,并拥有内联生产者和消费者函数根据工厂的类型调用特定于子类型的内部函数。的 factory.h 强>:
#ifndef FACTORY_H
#define FACTORY_H
#include <stdlib.h>
struct factory {
/* Common member across all factory types */
const int type;
/* Flexible array member to stop applications
from declaring static factories. */
const unsigned long data[];
};
/* Generic producer function */
void *produce(const struct factory *);
/* Generic factory discard function */
void retire(struct factory *);
/*
* Library functions that return factories.
*/
struct factory *mem_factory(const void *, const size_t);
#endif /* FACTORY_H */
和 factory.c :
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "factory.h"
enum {
INVALID_FACTORY = 0,
/* List of known factory types */
MEM_FACTORY,
/* 1+(the highest known factory type) */
NUM_FACTORY_TYPES
};
struct mem_factory {
int type;
size_t size;
char data[];
};
/* The visibility of this union ensures the initial sequences
in the structures are compatible; see C11 6.5.2.3p6.
Essentially, this causes the casts between these structure
types, for accessing their initial common members, valid. */
union all_factories {
struct factory factory;
struct mem_factory mem_factory;
};
/* All factories thus far implemented
are a single structure dynamically
allocated, which makes retiring simple.
*/
void retire(struct factory *factory)
{
if (factory &&
factory->type > INVALID_FACTORY &&
factory->type < NUM_FACTORY_TYPES) {
/* Poison factory type, to make it easier
to detect use-after-free bugs. */
factory->type = INVALID_FACTORY;
free(factory);
}
}
char *mem_producer(struct mem_factory *mem)
{
/* As a courtesy for users, return the memory
padded to a length multiple of 16 chars
with zeroes. No real reason to do this. */
const size_t size = (mem->size | 7) + 9;
char *result;
result = malloc(size);
if (!result) {
errno = ENOMEM;
return NULL;
}
/* Clear padding. */
memset(result + size - 16, 0, 16);
/* Copy data, if any. */
if (mem->size)
memcpy(result, mem->data, mem->size);
return result;
}
/* Generic producer function.
Calls the proper individual producers.
*/
void *factory_producer(struct factory *factory)
{
if (!factory) {
errno = EINVAL;
return NULL;
}
switch (factory->type) {
case mem_factory:
return mem_producer((struct mem_factory *)factory);
default:
errno = EINVAL;
return NULL;
}
}
/* Library functions that return factories.
*/
struct factory *mem_factory(const void *ptr, const size_t len)
{
struct mem_factory *mem;
if (!ptr && len > 0) {
errno = EINVAL;
return NULL;
}
mem = malloc(len + sizeof (struct mem_factory));
if (!mem) {
errno = ENOMEM;
return NULL;
}
mem->type = MEM_FACTORY;
mem->size = len;
if (len > 0)
memcpy(mem->data, ptr, len);
return (struct factory *)mem;
}
如果我们查看标准C和POSIX C库实现,我们会看到使用这两种方法。
标准I / O FILE
结构通常包含函数指针,fopen()
,fread()
,fwrite()
等函数只是这些函数的包装器。如果C库支持类似于GNU fopencookie()
的接口,则尤其如此。
POSIX.1套接字,尤其是struct sockaddr
类型,是本答案中首先显示的多态结构的原始原型。因为他们的界面不支持与fopencookie()
类似的任何内容(即覆盖例如send()
,recv()
,read()
,write()
,{{1 }},不需要函数指针。
所以,请不要问哪一个更合适,因为两者都是非常常用的,并且它在很大程度上取决于细节。一般来说,我更喜欢产生更简单实现的那个,提供所有必要的功能。
我个人发现,在没有实际经验和反馈的情况下担心未来的用例并没有用。而不是试图创建解决所有未来问题的最终所有,最好的框架,KISS principle和Unix philosophy似乎会产生更好的结果。
答案 1 :(得分:1)
正确的顺序是:
它不会以其他方式起作用。 ({})
不会为您弯曲语义。如果您的add
需要一个返回struct Super*
的函数,那么它将无法与struct Sub
一起使用,即使您将丢失的*
放在那里也不行。
这适用于TutorialsPoint:
#include <stdio.h>
#include <stdlib.h>
int max(int a,int b){
if(a>b)
return a;
return b;
}
struct Super{};
void add(struct Super *(*superRef)()) {
struct Super *(*secretStorage)()=superRef;
/* ... */
struct Super *super = secretStorage();
/* ... */
free(super);
printf("Stillalive\n");
}
int main()
{
printf("Hello, World!\n");
int (*myMax)(int,int); // <-- that is a function pointer
myMax=max; // <-- set with oldschool function
printf("%d\n",myMax(1,2));
myMax = ({ // <-- set with fancy magic
int __fn__ (int x, int y) { return x < y ? x : y; }
__fn__;
});
printf("%d - intentionally wrong\n",myMax(1,2));
add(
({
struct Super* fn(){
printf("Iamhere\n");
return malloc(sizeof(struct Super));
}
fn;}));
printf("Byfornow\n");
return 0;
}
创建了一个包含匿名魔法和堆分配的匿名魔法的小型库项目。它没有多大意义,但它确实有效:
testlib.h
#ifndef TESTLIB_H_
#define TESTLIB_H_
struct Testruct{
const char *message;
void (*printmessage)(const char *message);
};
extern struct Testruct *(*nonsense())();
#endif
testlib.c
#include "testlib.h"
#include <stdio.h>
#include <stdlib.h>
const char *HELLO="Hello World\n";
struct Testruct *(*nonsense())(){
return ({
struct Testruct *magic(){
struct Testruct *retval=malloc(sizeof(struct Testruct));
retval->message=HELLO;
retval->printmessage=({
void magic(const char *message){
printf(message);
}
magic;
});
return retval;
}
magic;
});
}
test.c的
#include "testlib.h"
#include <stdio.h>
#include <stdlib.h>
int main(){
struct Testruct *(*factory)()=nonsense();
printf("Alive\n");
struct Testruct *stuff=factory();
printf("Alive\n");
stuff->printmessage(stuff->message);
printf("Alive\n");
free(stuff);
printf("Alive\n");
return 0;
}
我按照https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html中的步骤构建了一个正在运行的步骤(几乎3 gcc来电:gcc -c -Wall -Werror -fpic testlib.c
,gcc -shared -o libtestlib.so testlib.o
,gcc -L. -Wall -o test test.c -ltestlib
并与{{1}进行了一些斗争}})
答案 2 :(得分:0)
经过多次努力,以下是解决方案,但感谢社区的帮助。
第一个社区告诉我,匿名函数不是C的一部分,因此备用建议是使用命名函数和指向它的指针。
其次,指向父结构的指针不能接收指向它的派生类型(嵌入式父结构)的指针,所以我在那里做不了多少。我尝试使用void *
但是也许使用内存地址可能存在解决方案,然后访问结构的某个成员而不转换为特定类型。我会问另一个问题。
我缺少的是能够以某种方式从重写的run方法调用super方法吗?
<强>的src / super.h 强>
struct Super {
void (*run)();
};
struct Super *newSuper();
<强>的src / super.c 强>
static void run() {
printf("Running super struct\n");
}
struct Super *newSuper() {
struct Super *super = malloc(sizeof(struct Super));
super->run = run;
return super;
}
<强>的src / Runner.h 强>
struct Runner {
void (*addFactoryMethod)(struct Super *(*ref)());
void (*execute)();
};
struct Runner *newRunner();
<强>的src / runner.c 强>
struct Super *(*superFactory)();
void addFactoryMethod(struct Super *(*ref)()) {
superFactory = ref;
}
static void execute() {
struct Super *sup = superFactory(); // calling cached factory method
sup->run();
}
struct Runner *newRunner() {
struct Runner *runner = malloc(sizeof(struct Runner));
runner->addFactoryMethod = addFactoryMethod;
runner->execute = execute;
return runner;
}
<强>测试/ runner_test.c 强>
void anotherRunMethod() {
printf("polymorphism working\n");
// how can i've the ability to call the overridden super method in here?
}
struct Super *newAnotherSuper() {
struct Super *super = malloc(sizeof(struct Super));
super->run = anotherRunMethod;
return super;
}
void testSuper() {
struct Runner *runner = newRunner();
runner->addFactoryMethod(&newAnotherSuper);
runner->execute();
}
int main() {
testSuper();
return 0;
}
答案 3 :(得分:0)
(引用您自己接受的答案)
其次,指向父结构的指针不能接收指向它的派生类型(嵌入式父结构)的指针,所以我在那里做不了多少。我尝试使用void *但是可能使用内存地址存在一个解决方案,然后访问结构的某个成员而不转换为特定类型。我会问另一个问题。
这是另一个指针,应该首先学习基础知识。你想念的东西叫做“前瞻宣言”:
struct chicken; // here we tell the compiler that 'struct chicken' is a thing
struct egg{
struct chicken *laidby; // while the compiler knows no details about 'struct chicken',
// its existence is enough to have pointers for it
};
struct chicken{ // and later it has to be declared properly
struct egg *myeggs;
};
我缺少的是能够以某种方式从重写的run方法调用super方法吗?
这些不是方法,也没有覆盖。在您的代码中没有OOP发生,C是一种过程编程语言。虽然有C语言的OOP扩展,但你真的不应该在不了解C基础知识的情况下使用它们。