包含类对象的最佳 C++ 设计是啥?

Posted

技术标签:

【中文标题】包含类对象的最佳 C++ 设计是啥?【英文标题】:What is the best C++ design to contain a class's objects?包含类对象的最佳 C++ 设计是什么? 【发布时间】:2015-02-01 01:54:41 【问题描述】:

我有一个包含许多对象的类,我想将它们分组到某种类型的容器中,并使用某种类型的标识符访问它们。

class Menu 
Object title;
Object play;
Object instructions;
Object pause;
...
;

在类中列出每个对象,如上所示,这很好,因为我可以像menu.title 一样访问它们,但是我必须重新键入每个名称才能将其添加到容器中,vector.push_back(title)

下面显示的是我一直解决问题的方法。我使用枚举的整数值来访问相应的索引。 objects[TITLE]objects[PLAY]

class Menu 
std::vector<Object> objects;
;

enum ObjectTypes 
TITLE, PLAY, INSTRUCTIONS, PAUSE, ...
;

我通常不喜欢这种方法,因为它看起来是间接的,并且在使用嵌套类和枚举时,标识符可能会变得冗长且繁琐。我来自 C 语言,对 C++ 有点陌生。有没有人有更实用和/或优雅的方法来解决这个问题?欢迎使用 C++11 及更高版本!

【问题讨论】:

你考虑过std::map吗?它是一个关联容器,这意味着您可以使用menu["title"] 引用对象,其中"title"std::string 您可以创建在众所周知的位置引用的方法,或者如果它是一个数组,那么在构造时绑定它们会很简单。 它也可以是一个 std::map. 我不想使用映射,因为这样我就有机会拼错字符串,或者我仍然需要一个单独的字符串列表或标识符容器,例如枚举。跨度> @Barry: 它不一定是向量,但我想对每个对象应用一些函数 【参考方案1】:

您使用的方法很好。如果你想避免繁琐的标识符,你可以做一个临时引用来保持更简洁。例如,而不是调用:

menu.objects[PAUSE].foo();
menu.objects[PAUSE].bar();
menu.objects[PAUSE].baz();

...您可以在必要时这样做:

Object & pause = menu.objects[PAUSE];
pause.foo();
pause.bar();
pause.baz();

它的工作原理相同,但没有所有冗余字符。

【讨论】:

【参考方案2】:

我认为你的方法很好。使用 std::map&lt;ObjectType, Object&gt; 而不是 std::vector 可能更安全一些。

无论哪种方式,如果您想节省一点打字时间,您可以在Menu 上重载operator[]

Object& operator[](index_type i) return objects[i]; 
const Object& operator[](index_type i) const  return objects[i];   

Then you can writemenu[TITLE].

除此之外,我看不出它怎么能不那么麻烦,那里没有多余的信息,正如 Jeremy 指出的那样,如果您多次需要一个对象,您总是可以创建一个本地引用 auto&amp; title = menu[TITLE]; .

根据Menu 的其他职责,也许您根本不需要Menu 类,您可以直接使用mapvector

【讨论】:

【参考方案3】:

您问题的最佳解决方案主要取决于您的用例。 我看到两个主要用例:

    您希望表示“设备”的“功能”,以便在从代码中操作该“设备”时获得可读的代码。例如具有播放、停止、暂停操作的 MediaPlayer 设备。但是您取消了简单地将成员函数添加到您的“设备”对象的选项,例如 Play(),因为您想将您的播放代码也重新用于另一个设备,例如调谐器设备。此外,您希望将操作应用于所有或这些“函数”的子集,例如 Enable()/Disable()/ToString()/Trigger(),Configure()...,这就是成员函数的原因方法不利。 您想要创建一个文档对象模型,该模型更注重数据。例如 Xml 文档。

根据您在问题中所写的内容,我假设您已经想到了用例 1。您的Object 类型具有您需要的所有常用操作。 然而,所有这些“功能”之间存在差异。

为了坚持您的简单方法,您需要手动设置/配置您的 Object 实例,从长远来看,这可能会很烦人,但很难避免:

// Example of "annoying": If you have more than 1 "device", 
// you have to write such a function for each of them.
// Also, there is that fishy bit with Object.Id - the association between
// your configuration data and your object you want to configure.
void ConfigureMenu( std::vector& menuItems, MenuConfigurationData& configData )

    for( auto& menuItem : menuItems )
    
         menuItem.Configure( configData[menuItem.Id] ); // spoiler!
    

另外,我倾向于认为即使现在您的问题中也没有显示一些代码,这些代码配置了您的对象。

考虑到这一点,您可能希望摆脱手动为每个“设备”编写 1 个类类型的想法。下一个设备/菜单将需要同样对待,但需要更多专门的编码。

所以,我对你的建议是永远摆脱你的class Menu,抽象你的问题并为你的问题建模,就像:对象是你的“功能”,设备只是一组功能/对象.然后,您的 class Menu 就变成了一个名为 Menu 的实例。

typedef uint32_t FunctionId; // for example uint32_t...
typedef std::map<FunctionId,Object> Device; // aka. Menu.

然后,无论如何,在您最有可能拥有的配置函数中,您传入该 Device 映射的实例,并且您的配置函数用正确配置的 Object 填充它。

// those enums are still specific to your concrete device (here Menu) but 
// you can consider the time it takes writing them an investment which will
// pay off later when you write your code, using your functions.
// You assign the function id which is used in your meta-data.
enum class MenuFunctions : FunctionId  play = ..., title = ..., instructions, ... ;

// "generic" configuration function.
// Does not only configure your Object, but also puts it inside the device.
void ConfigureDevice( Device& device, ConfigData& configData )
  // ...

稍后在您的代码中,您可以像这样访问函数:

menu[MenuFunctions::play].Trigger();

当然,还有其他方法和变体。例如,假设您有元数据(配置数据、设备描述),您可以停止手动编写所有这些代码,而是编写一些代码生成器来为您完成这项工作。 这种生成器的第一个版本可以为您创建配置函数和枚举。

有了所有这些,“嵌套类”的用例就变成了创建设备实例集合的问题。现在,由于您的所有设备都属于同一类型,您可以在闲暇时对它们进行组合和分组。

【讨论】:

【参考方案4】:

以下几种不同的方法。我不是说哪个是“最好的”。它们都有优点/缺点。

A) 当你的元素数量不变时,不要使用向量(例如class Menu std::vector&lt;Object&gt; objects; ;),而是使用数组class Menu std::array&lt;Object,NObjectTypes&gt; objects;;

B) 只需使用一个类,但提供一个 api 来返回对您的对象的引用的std::array&lt;&gt;

class Menu 
 Object title;
 Object play;
 Object instructions;
 Object pause;
...
 std::array<Object*,NObjects> allObjects();
;

C) std::tuple 在您的类型不完全相同时会很有用。


对于菜单,我通常会选择“A”。

【讨论】:

以上是关于包含类对象的最佳 C++ 设计是啥?的主要内容,如果未能解决你的问题,请参考以下文章

设置可以注入的填充对象的最佳方法是啥?

《C++程序设计POJ》《WEEK3 类和对象进阶》成员对象和封闭类/友元/this指针/常量成员函数

开心档之C++ 类 & 对象

c语言里的一等公民是啥意思

C++编程题 (面向对象程序设计)

在 C++ 中,动态分配单个类的最佳方法是啥? [关闭]