简体   繁体   English

在内部使用结构成员和函数指针

[英]Using struct member internally with function pointer

I have a struct defined as: 我有一个结构定义为:

typedef struct OneParamDist_t {
  double p;
  double (* rvs)(double);
} OneParamDist;

Such that allocating and calling looks like: 这样的分配和调用看起来像:

OneParamDist * flip = malloc(sizeof(OneParamDist));

flip->p = 0.5;
flip->rvs = bernoulli_rvs;
double x;
for(int i = 0; i < 100000; i++){
  x = flip->rvs(flip->p);
}

Ideally, I'd have: 理想情况下,我将拥有:

typedef struct OneParamDist_t {
  double p;
  double (* rvs)(p);
} OneParamDist;

such that calling it looks like: x = flip->rvs(); 这样调用它看起来像:x = flip-> rvs();

I just can't figure out how to do this, or whether this is even possible. 我只是不知道该怎么做,或者这是否可能。 Is this even possible? 这有可能吗? It feels like I'm trying to use light object orientation in C, and it might just not be used for it. 感觉就像我正在尝试在C中使用轻型对象方向,并且可能只是不使用它。

Object oriented code in is entirely possible, but all you get from the language is basically data structures and pointers. 语言中的面向对象代码是完全可能的,但是从该语言中获得的基本上是数据结构和指针。 Everything else is up to you. 其他一切都取决于您。

It helps to understand what "OO-Languages" typically already do internally. 它有助于了解“ OO语言”通常已经在内部执行的操作。 In , you would write your example probably like this: ,您可能会像这样编写示例:

#include <iostream>

class OneParamDist
{
    private:
        double p;

    public:
        OneParamDist(double p) : p(p) {}
        double rvs();
};

double OneParamDist::rvs()
{
    double result = this->p;
    // calculate something
    return result;
}

int main(void)
{
    OneParamDist flip(2.4);
    double result = flip.rvs();
    std::cout << "flip.rvs() = " << result << std::endl;
}

This code gives you a data structure that only contains p , no function pointers at all. 此代码为您提供了仅包含p的数据结构,根本没有函数指针。 The functions are the constructor (but it is empty, only used for initialization here) and rvs , but it goes by the name OneParamDist::rvs , so the compiler knows it "belongs" to OneParamDist . 函数是构造函数(但为空,仅在此处用于初始化)和rvs ,但其名称为OneParamDist::rvs ,因此编译器知道它“属于” OneParamDist

The function makes use of a pointer " this ". 该函数使用指针“ this ”。 So where does this come from? 那么, this是从哪里来的呢? The solution is simple: it's passed as a parameter. 解决方案很简单:将其作为参数传递。 You don't see it in the code because handles it for you. 您不会在代码中看到它,因为会为您处理它。 When compiling, 编译时

double OneParamDist::rvs();

is translated to something like 被翻译成类似

double mangled_name_for_OneParamDist_rvs(OneParamDist *this);

with mangled_name_for_OneParamDist_rvs being some "plain" name like in , the rules for generating this name are specific to the compiler used. mangled_name_for_OneParamDist_rvs是一些类似于 “普通”名称的情况下,生成该名称的规则特定于所使用的编译器。

With this knowledge, you can "translate" this simple program to plain quite easily: 有了这些知识,您可以很容易地将这个简单的程序“翻译”成普通的

#include <stdio.h>

typedef struct OneParamDist
{
    double p;
} OneParamDist;

#define OneParamDist_init(p) { .p = (p) }

double OneParamDist_rvs(OneParamDist *self)
{
    double result = self->p;
    // calculate something
    return result;
}

int main(void)
{
    OneParamDist flip = OneParamDist_init(2.4);
    double result = OneParamDist_rvs(&flip);
    printf("OneParamDist_rvs(&flip) = %lg\n", result);
}

I used my own naming scheme here, prefixing each "method" name with " [classname]_ ". 我在这里使用了自己的命名方案,在每个“方法”名称前都加上了“ [classname]_ ”。 I decided to use self instead of this , just to avoid confusion, it would be perfectly legal to use this in plain . 我决定使用self代替this ,只是为了避免混淆,在纯使用this完全合法。 But this would of course break compatibility with . 但这当然会破坏与兼容性。


Basically, the answer is: Accept the fact that for an OO method, you always need a pointer to the object as a parameter (by convention the first parameter) and that doesn't pass that pointer automatically for you like does. 基本上,答案是:接受以下事实: 对于OO方法,您总是需要一个指向对象的指针作为参数 (按照惯例,第一个参数),并且不会像那样自动为您传递该指针。 Create your code accordingly. 相应地创建代码。


Back to your idea of having a function pointer in the struct , when should you do this? 回到您在struct中包含函数指针的想法,您什么时候应该这样做? Take a look at the virtual keyword of . 看一下virtual关键字。 It allows you do derive from your class and have the method of this derived class called even through a pointer to the base class. 它允许您从类中派生并让该派生类的方法甚至通过指向基类的指针来调用。 If you think about it, this requires the pointer to the method being available somewhere. 如果您考虑一下,这需要在某个地方可以找到该方法的指针。 solves this typically using vtable objects, structures containing only the function pointers. 通常使用vtable对象(仅包含函数指针的结构)解决此问题。 If you roll your own "virtual" methods, putting them inside your object as function pointers is the most straightforward way. 如果滚动自己的“虚拟”方法,则将它们作为函数指针放入对象中是最直接的方法。 But as long as you don't need virtual methods, don't use function pointers, they're just overhead for nothing to gain. 但是,只要您不需要虚方法,就不要使用函数指针,它们只是开销而已,一无所获。


Another thing worth mentioning is that with , you can achieve a perfect level of information hiding (like the pimpl idiom) if you always allocate your objects dynamically. 值得一提的另一件事是, 如果您始终动态分配对象,则使用可以达到完美的信息隐藏水平(如 pimpl习惯用法)。 In that case, you can hide the entire definition of the struct from the callers. 在这种情况下,您可以对调用方隐藏该struct的整个定义。 Your example could then look like this: 您的示例如下所示:

oneparamdist.h 一个参数

#ifndef ONEPARAMDIST_H
#define ONEPARAMDIST_H

typedef struct OneParamDist OneParamDist;

OneParamDist *OneParamDist_create(double p);
double OneParamDist_rvs(OneParamDist *self);
void OneParamDist_destroy(OneParamDist *self);

#endif

oneparamdist.c 一个参数

#include <stdlib.h>
#include "oneparamdist.h"

struct OneParamDist
{
    double p;
};

OneParamDist *OneParamDist_create(double p)
{
    OneParamDist *self = malloc(sizeof(*self));
    if (!self) return 0;
    self->p = p;
    return self;
}

double OneParamDist_rvs(OneParamDist *self)
{
    double result = self->p;
    // calculate something
    return result;
}

void OneParamDist_destroy(OneParamDist *self)
{
    if (!self) return;
    free(self);
}

example.c example.c

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

#include "oneparamdist.h"

int main(void)
{
    OneParamDist *flip = OneParamDist_create(2.4);
    if (!flip) return EXIT_FAILURE;
    double result = OneParamDist_rvs(flip);
    printf("OneParamDist_rvs(flip) = %lg\n", result);
    OneParamDist_destroy(flip);
}

C provides none of the syntactic sugar that C++ does. C没有提供C ++所提供的语法糖。 You can still write object-oriented code in C, but you need to pass the this pointer explicitly. 您仍然可以用C编写面向对象的代码,但是您需要显式传递this指针。

It's not possible to bind the function pointer to always accept p as the first argument as you are trying to do no. 试图将函数指针绑定为始终不接受p作为第一个参数是不可能的。 This is a limitation of C as a non-object oriented language. 这是C作为非面向对象语言的局限性。

Probably the easiest thing to do is just accept this. 也许最容易做的就是接受这一点。 Don't think of C structs as being like C++ classes but just as what they are - structured data containers. 不要认为C结构像C ++类,而是像它们一样-结构化数据容器。

However, if you really need your code to work in the way you use it, the most straightforward way I can think of is to make pa module-level variable that the rvs function can access directly, with a pointer to the external p stored on the struct. 但是,如果您确实需要代码以使用方式工作,那么我想到的最直接的方法是,使rvs函数可以直接访问的pa模块级变量与指向外部p的指针一起存储在结构。

#include <stdlib.h>

double p;

typedef struct OneParamDist_t {
    double *p;
    double (* rvs)(void);
} OneParamDist;

double rvs_func(void);

int main(void)
{
    OneParamDist *flip = malloc(sizeof(OneParamDist));
    extern double p;

    p = 2.0;
    flip->p = &p;
    flip->rvs = rvs_func;

    flip->rvs();  // returns 4.0
    flip->rvs();  // returns 8.0
    *flip->p      // is now 8.0

    return 0;
}

double rvs_func(void)
{
    extern double p;
    p *= 2;
    return p;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM