包含类对象的最佳 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您使用的方法很好。如果你想避免繁琐的标识符,你可以做一个临时引用来保持更简洁。例如,而不是调用:
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<ObjectType, Object>
而不是 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& title = menu[TITLE];
.
根据Menu
的其他职责,也许您根本不需要Menu
类,您可以直接使用map
或vector
?
【讨论】:
【参考方案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<Object> objects; ;
),而是使用数组class Menu std::array<Object,NObjectTypes> objects;;
。
B) 只需使用一个类,但提供一个 api 来返回对您的对象的引用的std::array<>
:
class Menu
Object title;
Object play;
Object instructions;
Object pause;
...
std::array<Object*,NObjects> allObjects();
;
C) std::tuple
在您的类型不完全相同时会很有用。
对于菜单,我通常会选择“A”。
【讨论】:
以上是关于包含类对象的最佳 C++ 设计是啥?的主要内容,如果未能解决你的问题,请参考以下文章