【问题标题】:Elegant way to emulate 'this' pointer when doing OOP in C?在 C 中执行 OOP 时模拟“this”指针的优雅方式?
【发布时间】:2012-05-03 05:44:19
【问题描述】:

我想在 C 中使用多态性进行一些面向对象风格的编程,其中我的接口类包含一个指向函数表的指针。例如:

/* Implement polymorphism in C, Linux kernel-style */
struct statement {
    const struct statement_ops *ops;
    struct list_head list;      /* when on master input list */
    void *private;          /* pointer to type-specific data */
};

struct statement_ops {
    int (*analyse)(void *private, int pc);
    int (*get_binary_size)(void *private);
};

void user(void)
{
    struct statement *s = make_a_statement();
    if (s->ops->analyse(s->private, foo))
        blah blah;
}

我希望能够在不将 s->private 显式传递给每个“方法”的情况下编写一些东西。有任何想法吗?可能有一些宏观技巧?

【问题讨论】:

    标签: c oop polymorphism


    【解决方案1】:

    如果这是公共接口的一部分,您可以添加访问器函数。一个隐藏的好处是您可以在访问器中进行完整性检查和其他工作。 (请注意,我将“this”指针称为“o”,就像在“object”中一样。为了保持一致性,我更喜欢这种方式。)

    int statement_analyse (struct statement *o, int pc)
    {
        assert(pc >= 0);
    
        int ret = o->ops->analyse(o->private, pc);
        assert(ret >= 0);
    
        return ret;
    }
    

    您现在可以在不显式传递“private”的情况下调用它。

    void user(void)
    {
        struct statement *s = make_a_statement();
    
        if (statement_analyse(s, foo))
            blah blah;
    }
    

    虽然这似乎没有任何好处,因为您仍然必须实现访问器,假设您想要一个定义良好且健壮的接口,访问器函数是放置断言和接口文档的唯一明智的地方。事实上,如果你编写好的断言,断言本身有助于记录接口。一旦你在访问器中添加了完整性检查,你就不必在它们调用的实际方法中添加它们。

    当然,这种方法只有在通过函数指针调用的函数是用户提供的东西时才有意义,或者以其他方式可以是不同的东西。如果有一个 analyse() 方法总是做同样的事情,你可以简单地实现一个 statement_analyse() 直接做它需要做的事情。

    小提示:在做 OOP 时,我更喜欢 typedef 结构并给它们 CamelCase 名称。我使用这个约定来说明该结构是不透明的,并且只能通过其公共接口访问。它看起来也更好,尽管这是主观的。我也更喜欢让用户为结构本身分配内存,而不是构造函数分配它。这样可以避免处理 malloc 失败,并使程序更高效。

    typedef struct {
        ...
    } Statement;
    
    void Statement_Init (Statement *o);
    int Statement_Analyse (Statement *o, int pc);
    

    【讨论】:

    • 很公平,我想这已经差不多了,可能会有宏包装。我个人厌恶 CamelCase :)
    【解决方案2】:

    不幸的是,编写方法以允许传递 selfthis 对象是在 C 中实现此目的的唯一方法。

    您可以使用宏技巧来隐藏其中的一部分,但此时它不再是真正的 C。

    【讨论】:

      【解决方案3】:

      正如其他答案所说,如果不使用适当的指针调用函数,就无法做到这一点,但是(正如 Williham Totland 建议的那样)您可以使用宏来简化调用(需要具有 variadic macro 支持的编译器) :

      // macro_call.c
      
      #define C_ARGS(stmnt, func, ...) (stmnt)->ops->func((stmnt)->private, ...)
      #define C_NOARGS(stmnt, func) (stmnt)->ops->func((stmnt)->private)
      
      C_ARGS(s, analyse, 1);
      
      C_ARGS(s, lots_of_args, 1, 2, 3, 4);
      C_NOARGS(s, no_args);
      

      C 用于“通话”。)

      对其进行预处理(通过gcc -E macro_call.c)给出:

      (s)->ops->analyse((s)->private, 1);
      
      (s)->ops->lots_of_args((s)->private, 1, 2, 3, 4);
      (s)->ops->no_args((s)->private);
      

      这类似于访问函数版本:宏版本在某些方面稍微灵活一些,但也不太安全,可能会导致细微的错误和错误。

      有两个宏,因为不向C_ARGS 传递额外的参数会导致s->ops->func(s->private, ),我认为可以解决这个问题,但它很尴尬并且需要更多的代码(空__VA_ARGS__ 是出了名的难处理)。

      【讨论】:

      • 我曾想过这样的事情。我经常使用像 #define DBG(fmt, args...) printf(fmt, ##args) 这样的宏,它们可以在没有提供参数的情况下正常工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-29
      • 2012-08-11
      • 2013-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多