【问题标题】:UML representation for C/C++ function pointersC/C++ 函数指针的 UML 表示
【发布时间】:2012-12-01 20:31:06
【问题描述】:

什么是 C/C++ 函数指针 (fp) 在 UML 结构图中的最佳表示?

我正在考虑使用界面元素,即使“退化”也可能会受到最多声明一个操作的约束。

我在此文档中找到了一些建议:C and UML Synchronization User Guide, Section 5.7.4。但这听起来很麻烦,在实践中也不是很有用。即使从非常低级别的语义角度来看也是如此。下图简要展示了他们的概念:

恕我直言,C 和 C++ 函数指针被用作接口的狭义视图,它只提供单个函数及其签名。在 C fp 中也可以用于实现更复杂的接口,声明一个包含一组函数指针的结构。

我认为我什至可以设法让我的特定 UML 工具(企业架构师)转发生成正确的代码,并与代码更改同步而不会造成伤害。

我的问题是:

  1. 将 fp 声明为 UML 中界面元素的一部分是否会提供正确的语义视图?
  2. 单fp声明应该使用什么样的构造型?至少我需要在代码中提供一个 typedef 所以这将是我的胆量选择。(我发现这个原型是 Enterprise Architect 专有的)并且我需要定义一个合适的原型来适应代码生成.实际上我选择了原型名称“委托”,这是否有任何含义或语义冲突?
  3. 对于 C++,是否在类元素中嵌套“委托”原型接口足以正确表达类成员函数指针?

这是我对 C 语言表示的想法的示例图:

这是应该从上述模型生成的 C 代码:

struct Interface1;

typedef int (*CallbackFunc)(struct Interface1*);

typedef struct Interface1
{
    typedef void (*func1Ptr)(struct Interface1*, int, char*);
    typedef int (*func2Ptr)(struct Interface1*, char*);
    typedef int (*func3Ptr)(struct Interface1*, CallbackFunc);

    func1Ptr func1;
    func2Ptr func2;
    func3Ptr func3;

    void* instance;
};

/* The following extern declarations are only dummies to satisfy code
 * reverse engineering, and never should be called.
 */
extern void func1(struct Interface1* self, int p1, char* p2) = 0;
extern int func2(struct Interface1* self, char*) = 0;
extern int func3(struct Interface1* self, CallbackFunc p1) = 0;

编辑:
整个问题归结为手头的 UML 工具及其特定代码工程功能的最佳方式。因此我添加了 标签。

【问题讨论】:

  • @garzanti 如果您的问题很严重,这是一种原始数据类型,我已经提到了这一点(按照链接的文档)。
  • 我很认真,但确实是修辞,不是取笑你或其他任何人。我只是相信有时我们必须回归基础。
  • @garzanti 至少我在这里读德语很困惑。但无论如何,我看到使用 uml:DataType 看起来与我的提议相似,不幸的是,我无法使用我的 UML 工具(这可能是正确的)将 DataTypes 嵌套在类中,并且需要额外的 typedef,这使得图表不太清楚这是有意的。
  • @garzanti 我必须纠正自己:函数指针不是 > 而是 >。
  • 我认为这种方法一开始就是错误的。为什么需要对函数指针对象建模?

标签: enterprise-architect c++ c uml enterprise-architect


【解决方案1】:

EA 的帮助文件中关于函数指针的主题如下:

在导入 C++ 源代码时,Enterprise Architect 会忽略函数指针声明。要将它们导入您的模型,您可以创建一个 typedef 来定义函数指针类型,然后使用该类型声明函数指针。以这种方式声明的函数指针作为函数指针类型的属性导入。

注意“可以”。这是来自 C++ 部分,C 部分根本没有提到函数指针。因此它们没有得到很好的支持,这当然是由于建模和编程社区之间的差距:UML 根本不支持非平凡的语言概念,因此任何解决方案都必然是特定于工具的。

我的建议有点牵强,而且有点老套,但我认为它应该工作得很好。

因为在 UML 中操作不是一流的,不能用作数据类型,我的回应是为它们创建一流的实体——换句话说,将函数指针类型定义为类。

这些类有两个用途:类名将反映函数的类型签名,以使程序员在图中看起来很熟悉,而一组标记值将表示实际参数和返回类型,以用于代码生成。

0) 您可能希望为步骤 1-4 设置 MDG 技术。

1) 使用详细信息“Type=RefGUID;Values=Class;”定义标记值类型“retval”

2) 进一步定义一组具有相同详细信息的标记值类型,名为“par1”、“par2”等。

3) 使用包含“retval”标记值(但没有“par”标记)的类构造型“funptr”定义配置文件。

4) 修改代码生成脚本属性声明和参数以检索“retval”(始终)和“par1”-“parN”(已定义)并为它们生成正确的语法。这将是棘手的一点,我实际上并没有这样做。我认为无需太多努力就可以完成,但是您必须尝试一下。您还应该确保没有为“funptr”类定义生成代码,因为它们代表匿名类型,而不是 typedef。

5) 在您的目标项目中,定义一组类来表示原始 C 类型。

这样,您可以将函数指针类型定义为 «funptr» 类,其名称类似于“long(*)(char)”,用于接受 char 并返回 long 的函数。

在“retval”标签中,选择您在第 4 步中定义的“long”类。

手动添加“par1”标签,如上选择“char”类。

您现在可以将此类用作属性或参数的类型,或 EA 允许类引用的任何其他位置(例如在不同的 «funptr» 类的“par1”标签中;这使您可以轻松创建参数之一是函数指针类型的函数的指针类型)。

这里最骇人听闻的一点是编号的“par1”-“parN”标签。虽然在 EA 中可以定义多个具有相同名称的标签(您可能必须更改标签值窗口选项才能看到它们),但我认为您无法在代码生成脚本中检索不同的值(即使你可以我认为不一定会保留顺序,并且参数顺序在C中很重要)。所以你需要事先决定参数的最大数量。在实践中不是一个大问题;设置说 20 个参数应该足够了。

此方法对逆向工程没有帮助,因为 EA 9 不允许您自定义逆向工程过程。然而,即将到来的 EA 10(目前在 RC 1 中)将允许这样做,虽然我自己没有看过它,所以我不知道这会采取什么形式。

【讨论】:

  • 感谢@Uffe,这听起来非常可行,可以正确生成代码,并在图表中清楚地显示这是什么类型的东西。我认为让par1parN 标签需要有一定的数量限制,必须在代码生成模板中应用。
  • 是的,每个 parX 都需要自己的模板代码 sn-p,例如在三参数 funptr 类中删除“par2”会导致问题,这就是为什么我说它有点 hacky .如果您想全力以赴,当然可以编写一个插件来帮助保持结构完好无损。
  • 当我要为引用<<funptr>> 的属性或参数生成代码时,我可以(需要我)访问除类名之外的任何内容吗?现在我只是使用类名并将(*)替换为(*<param/attr-name>),也有点hacky ...
  • 你好,我喜欢你描述实现它的方式。但是,我只做了第 2、3 和 5 步,其余的都坚持了下来。我可以请您完善描述,例如一步一步的快照。非常感谢!
【解决方案2】:

函数指针的定义超出了 UML 规范的范围。更重要的是,它是许多 UML 建模软件不支持的特定于语言的特性。因此,我认为您对第一个问题的一般回答建议避免使用此功能。您提供的技巧仅与 Enterprise Architect 相关,并且与其他 UML 建模工具不兼容。以下是其他一些 UML 软件支持函数指针的方式:

MagicDraw UML 对 FP 类成员使用 C++FunctionPtr>> 构造型,对函数原型使用 C++FunctionSignature>>。

代码示例(取自official site -- 请参阅“C++ 代码生成的建模 typedef 和函数指针”小视图):

class Pointer
{
    void (f*) ( int i );
}

对应的UML模型:

Objecteering用对应的C++ TypeExpr注释定义FP属性。

IBM

Rational Software Architect 不支持函数指针。用户可以将它们添加到在代码->UML 和 UML->代码转换期间保持不变的用户定义部分中生成的代码。

【讨论】:

  • 是的,我同意这种语言特定功能的表示取决于您的特定工具的最佳支持。但是恕我直言,函数指针、委托等有一个合理的 UML 表示。链接文档中的 DataType 方法(嗯,也是特定于工具的),我认为它描述得很好。
【解决方案3】:

对我来说似乎是正确的。我不确定您是否应该深入了解描述单个函数指针的类型和关系的低级细节。我通常发现描述一个接口就足够细化了,不需要分解它的内部元素。

【讨论】:

    【解决方案4】:

    我认为你可以用一个类虚拟地包装函数指针。我认为 UML 不必是代码的蓝图级别,记录概念更重要。

    【讨论】:

      【解决方案5】:

      我的感觉是您希望将 UML 接口映射到 struct-with-function-pointers C 习惯用法。

      Interface1 是模型中的重要元素。到处声明函数指针对象类型会使您的图表难以辨认。

      Enterprise Architect 允许您指定自己的代码生成器。寻找Code Template Framework。您应该能够借助一两个新的构造型来修改预先存在的 C 代码生成器。

      【讨论】:

      • 感谢您的提示。我已经在深入调整 EA 的 CTF。问题不在于结构函数指针习语的接口,这很容易通过代码生成来处理。它更多地是关于如何对用作回调的单个 fp 进行建模。
      • 我明白了。由于 UML 没有委托原语,因此您必须为每个回调类型生成一个新类。
      • @Pedro_Lamarão '由于 UML 没有代表的原语......' 是的,这就是重点。 UML 通常没有指针类型的标题,尽管它们通常可以使用UML:dataType 建模,这最终是“正确的方式”恕我直言。但是我很难用 EA 的代码生成和逆向工程工具来正确处理这个视图。这就是为什么至少我决定采用 Uffe 的提议,因为它为 EA C/C++ 类模型提供了“最简单”的使用方式,并在查看 fp 作为参数或属性时提供了适当的视觉提示。
      • 鉴于您正在建模的行为,我认为 dataType 是不够的。您不是在建模指针,而是在回调,其形式与命令模式相同。你会想用抽象回调类型和它的各种特化之间的泛化关系来描述它。这些特定类型必须在 C 中表示为指针类型的事实是“实现”的细节。同样,如果回调类型层次结构带有一些巧妙的原型注释,您可以让您的代码生成器执行正确的操作。
      • 我必须承认,我们现在已经进入了我在实践中不知道的领域。
      【解决方案6】:

      我已经能够与 Enterprise Architect 合作。它有点笨拙的解决方案,但它满足了我的需求。我做了什么:

      1. 创建一个名为 FuncPtr 的新类原型。我在这里按照指南进行操作:http://www.sparxsystems.com/enterprise_architect_user_guide/10/extending_uml_models/addingelementsandmetaclass.html 当我这样做时,我为配置文件创建了一个新视图。所以我可以将它包含在我的主要项目之外。

      2. 修改了类代码模板。基本上选择 C ​​语言并从 Class Template 开始,然后点击“Add New Stereotype Override”并添加 FuncPtr 作为新的覆盖。

      3. 将以下代码添加到该新模板:

      %PI="\n"%
      %ClassNotes%
      typedef %classTag:"returnType"% (*%className%)(
      %list="Attribute" @separator=",\n" @indent="    "%
      );
      
      1. 修改了属性声明代码模板。和以前一样,添加一个新的 Stereotype

      2. 将以下代码添加到新模板中:

      %PI=""% %attConst=="T" ? "常量" : ""%

      %attType%

      %attContainment=="通过引用" ? "*" : ""%

      %attName%

      这就是我在 Enterprise Architect 中获得函数指针所需要做的一切。当我想定义一个函数指针时,我只是:

      1. 创建一个常规类
      2. 在标签'returnType'中添加我想要的返回类型
      3. 为参数添加属性。

      这样它将创建一个新类型,该类型可以作为属性或参数包含在其他类(结构)和运算符中。我没有将其设为运算符本身,因为那样它就不会在工具内作为您可以选择的类型被引用。

      所以它有点hacky,使用特殊的原型类作为函数指针的typedef。

      【讨论】:

      • 你能给出任何改进吗,这实际上会生成有问题的代码,并且会被正确地进行逆向工程?
      • 您应该注意,这个问题已经得到了很好的回答(我终于保护它不知道进一步的答案)。如果您真的想贡献,请edit your answer 展示图表示例、生成的代码等。
      【解决方案7】:

      像您的第一个示例一样,我将使用分类器,但将其隐藏在配置文件中。我认为他们已经将它包括在内以清楚地解释这个概念;但在实践中,刻板印象的整个想法是将细节抽象到配置文件中,以避免“噪音”问题。 EA 非常适合处理配置文件。

      我与您的第一个示例的不同之处在于我将分类 原始类型 刻板印象而不是 数据类型 刻板印象。数据类型是一个域范围对象,而原始类型是一个原子元素,其语义定义在 UML 范围之外。这并不是说你不能添加注释,尤其是在配置文件中,或者给它一个非常清晰的原型名称,比如 functionPointer。

      【讨论】:

      • 原始类型不能有操作,所以我无法指定必要的操作签名,以区分各种函数指针。我已经为我在第二个示例中介绍的刻板印象开始了一个配置文件,我无法通过使用 EA 生成代码来正确处理 dataType 刻板印象。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-28
      • 2011-08-19
      • 2012-04-03
      • 2011-08-15
      • 2020-05-17
      相关资源
      最近更新 更多