简体   繁体   中英

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();

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.

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. 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. 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 .

The function makes use of a pointer " this ". So where does this come from? 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.

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]_ ". I decided to use self instead of this , just to avoid confusion, it would be perfectly legal to use this in plain . 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. Create your code accordingly.


Back to your idea of having a function pointer in the struct , when should you do this? Take a look at the virtual keyword of . 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. 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. In that case, you can hide the entire definition of the struct from the callers. 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

#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. You can still write object-oriented code in C, but you need to pass the this pointer explicitly.

It's not possible to bind the function pointer to always accept p as the first argument as you are trying to do no. This is a limitation of C as a non-object oriented language.

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.

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.

#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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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