包含 class 对象的最佳 C++ 设计是什么?

What is the best C++ design to contain a class's objects?

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

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

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

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

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

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

我通常不喜欢这种方法,因为它似乎是间接的,而且当使用嵌套的 classes 和枚举时,标识符会变得又长又麻烦。我来自 C,对 C++ 有点陌生。有人有更实用的 and/or 优雅的方法来解决这个问题吗?欢迎使用 C++11 及更高版本!

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

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

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

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

它的工作原理是一样的,但没有所有冗余字符。

我觉得你的方法很好。使用 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 write menu[TITLE].

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

根据 Menu 的其他职责,您可能根本不需要 Menu class,您可以只使用 mapvector 直接?

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

  1. 您想表示 "functions" 的 "device" 以便在从您的代码中操作 "device" 时获得可读代码。如具有播放、停止、暂停操作的MediaPlayer设备。但是您放弃了将成员函数简单地添加到您的 "device" 对象的选项,例如 Play(),因为您还想将您的播放代码重新用于另一个设备,例如调谐器设备。此外,您希望将操作应用于所有 "functions" 或其中的子集,例如 Enable()/Disable()/ToString()/Trigger(),Configure()...,这就是为什么成员函数方法不受欢迎。
  2. 您想创建一个更加注重数据的文档对象模型。例如 Xml 文档。

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

为了坚持您的简单方法,您需要手动设置 up/configure 您的 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!
    }
}

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

考虑到这一点,您可能希望摆脱为每个 "device" 手写 1 class 类型的想法。下一个 device/menu 将需要同样对待,但需要更多的专门编码。

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

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

然后,在您最有可能拥有的配置函数中,您传入该 Device 映射的一个实例,然后您的配置函数用正确配置的对象填充它。

// 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();

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

有了所有这些,"nested classes" 的用例就变成了创建设备实例集合的问题。现在,由于您所有的设备都是同一类型,您可以随意组合和分组它们。

以下几种不同的方法。我不是说哪个是 "best"。总共有pros/cons。

A) 当元素数不变时,不要使用向量(例如 class Menu { std::vector<Object> objects; };),而是使用数组 class Menu {std::array<Object,NObjectTypes> objects;};

B) 只需使用 class,但提供 api 到 return 对对象的 std::array<> 引用:

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

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


对于菜单,我通常会使用 "A"。