【问题标题】:Function naming conventions mangling / overloading in cc中的函数命名约定重载/重载
【发布时间】:2012-07-14 21:59:34
【问题描述】:

假设我有多个功能可以实现相同的目标。例如,为数据类型创建“构造函数”。

String new_String();
String new_String(const char *cstr);
String new_String(String s);

显然这不能在 C 中完成,但是对于这些函数(以及函数重载很有用的情况)是否有命名它们的约定或最佳实践?

这样的?

String new_String();
String new_String_c(const char *cstr);
String new_String_s(String s);

这对我来说感觉很尴尬且不容易阅读。还是这样的?

String new_String();
String new_String_from_cstr(const char *str);
String new_String_copy(String s);

这让我想起了可怕的长 java 名称,因为这很快就会变得荒谬。

int String_last_index_of_any_characters(String s, char *chars, int length);

【问题讨论】:

  • 这必须是 C 项目吗?与其尝试在 C 中模拟 C++,不如在 C++ 中编写代码?
  • 一个项目是c而不是c++的原因有很多。
  • 你编程过 GTK+ 吗?它有那些长得可笑的 C 函数名称,例如 gtk_icon_view_convert_widget_to_bin_window_coords()

标签: c coding-style


【解决方案1】:

您可以定义一个带有 void * 初始化参数和枚举的“构造函数”:

enum StringCtor { SC_DEFAULT, SC_C_STR, SC_COPY };

String new_String(enum StringCtor type, const void *arg);

String s1 = new_String(SC_DEFAULT, 0);
String s2 = new_String(SC_C_STR, "hello");
String s3 = new_String(SC_COPY, &s2);

您也可以选择使用... 而不是void *。想法是一样的,但是如果参数应该是字符串或副本,则使用va_* 宏来提取参数。

如果您只希望 API 具有单个构造函数的外观,但仍希望类型安全,则可以使用上述技术来创建实际的构造函数实现,并使用内联函数和预处理器技巧使外观看起来像单一构造函数,具有类型安全性。

String new_StringImpl(enum StringCtor type, const void *arg);

static inline String new_StringImplDefault () {
    return new_StringImpl(SC_DEFAULT, 0);
}
static inline String new_StringImplCstr (const char *s) {
    return new_StringImpl(SC_C_STR, s);
}
static inline String new_StringImplCopy (String *s) {
    return new_StringImpl(SC_COPY, s);
}

#define new_String_Paste(TYPE) new_String_ ## TYPE
#define new_String_SC_DEFAULT(ARG) new_StringImplDefault()
#define new_String_SC_C_STR(ARG) new_StringImplCstr(ARG)
#define new_String_SC_COPY(ARG) new_StringImplCopy(ARG)

#define new_String(TYPE, ...) new_String_Paste(TYPE)(__VA_ARGS__)

String s1 = new_String(SC_DEFAULT);
String s2 = new_String(SC_C_STR, "hello");
String s3 = new_String(SC_COPY, &s2);

注意可变参数宏,SC_DEFAULT 不再需要第二个参数。在足够的优化级别,代码仅转换为对单个实现函数的调用,并具有编译时类型安全检查的好处。因此,以您需要更多编码为代价,您可以为您的库的用户提供单个构造函数 API 的外观,并具有多个构造函数的所有类型安全性。

【讨论】:

  • 是否需要new_StringImpl(enum StringCtor type ...?是否有理由不只是将 3 个内联函数用于不同的实现,而不是将其传递给 catch all 函数?
  • @Nat:不,这不是必需的。如果您愿意,您可以完全实现每个单独的功能。我只是将单个函数用作类型安全传递。
【解决方案2】:

没有标准约定,但如果有许多或多或少相似的函数重载,人们通常会参考OpenGL function naming convention

method provided by @user315052 通常不是首选,因为类型安全问题(容易出错、难以更改等)和/或当您要传递多个参数时会带来不便。但有时出于不同的技术原因使用它(内核系统调用就是一个很好的例子)。

【讨论】:

  • 虽然我同意“只使用void*”作为另一个答案通常是非常糟糕的建议,但我同样怀疑“人们通常指的是 OpenGL 函数命名约定”。遵循该约定的其他(最好是有信誉的)项目的例子是什么?他们如何处理字符串,因为这是这里的问题并且该链接没有提及它?另外,我注意到他们建议傻typedefs 为基本类型,这总是很傻,充其量是历史遗迹,不应该用任何几乎现代的代码编写。我确信他们的风格适合他们,但并不意味着它适用于其他人
【解决方案3】:

我不认为关于如何处理重载有一个完善的约定。您的第二种方法很好;使用常识使名称保持合理简短但仍易于理解。

在某些情况下,像您的第一种方法(或 OpenGL 中的方法)这样的系统命名约定可能是一个好主意,但它会使代码不那么自解释;任何阅读您的代码的人都需要先了解您的命名约定。

【讨论】:

    【解决方案4】:

    现代 C,又名 C11,可以做的比你想象的要多。它具有类型泛型宏,可以使用新的 _Generic 表达式完成类似于 C++ 中的函数重载的操作。

    作为库的内部接口,您仍然需要函数声明:

    String new_String_from_cstr(const char *str);
    String new_String_copy(String s);
    

    现在您的类型泛型宏可能如下所示:

    #define new_String(X)                       \
    _Generic((X),                               \
             const char*: new_String_from_cstr, \
             String: new_String_copy)(X)
    

    C11 还没有完全实现,但我认为最新的 clang 编译器已经有你需要的了。

    对于其他编译器(gcc 和表兄弟),您可以查看P99,它模拟了接近于此的功能。 P99 也有处理 receive different numbers of parameters 的宏的技巧。

    如果您使用诸如用户界面之类的工具,那么函数本身的命名约定就变得不那么重要了。

    【讨论】:

      猜你喜欢
      • 2017-03-19
      • 1970-01-01
      • 2015-07-05
      • 2014-06-18
      • 2011-12-31
      • 2022-12-22
      • 2011-02-10
      相关资源
      最近更新 更多