一个拥有很多成员的班级的最佳实践

10
关于在C++中组织类成员的最佳方法(尤其是在成员很多时),有什么意见吗?特别是,一个类有很多用户参数,例如优化某些函数的类,具有诸如迭代次数、优化步骤大小、使用的特定方法、优化函数权重等参数。我尝试过几种常用方法,似乎总能发现一些非理想的东西。只是好奇其他人的经验。
具体来说,我正在处理图像序列中对象的跟踪,因此需要在帧之间保持状态(这就是为什么我没有只编写一些函数)。重要的成员函数包括initTrack()、trackFromLastFrame()、isTrackValid()等。还有许多用户参数(例如每个跟踪对象跟踪多少个点、点在帧之间可以移动多少、使用的跟踪方法等等)。

  1. 在类内部定义结构体
  2. 在类外部定义结构体
  3. 公共成员变量
  4. 私有成员变量,并使用Set()和Get()函数

2
类的参数是什么意思?成员?模板参数?还是其他什么? - Steve Townsend
6
如果它有这么多成员,那么你的类做得太多了,需要重构你的代码。 - GManNickG
你谈论一个类,但你似乎在描述一个函数。 :-/ - Šimon Tóth
有趣的问题,但如果您提供更具体的示例可能会更有帮助。比如成员之间的关系。我可以看到将它们全部打包在一个结构体中,在构造期间简化参数传递可能会很有用。您能否发布一些代码呢? - n1ckp
我有一个名为GraphicsVertex的类,其中有5个成员变量(指针)。当我的应用程序运行时,它会创建大约5000万个对象。现在对于每个对象,它将占用5个成员* 5000万内存。我该如何避免这种情况? - DigviJay Patil
7个回答

6
如果你的类很,那么你的类就是糟糕的。 一个类应该遵守单一职责原则,即:一个类只应该做一件事情,但应该做到做好。 (“只有一个”是极端的,但它应该只有一个角色,并且必须清晰地实现)。
然后,您可以创建使用这些单一角色小类进行组合丰富的类,每个小类都具有明确简单的角色。 函数和类会引发错误,造成误解和意外的副作用(特别是在维护期间),因为没有人可以在几分钟内学习700行代码。
因此,类的策略是:重构,使用只针对其所需的内容进行小型类的组合。

7
参数的数量多并不意味着类很大。这个类可能只做一件事,但需要很多参数。 - Adrian McCarthy
1
@Adrian McCarthy,我只能引用一句话来回答你:“如果你的函数需要10个参数,那么你很可能忘记了其中一个”。 - Stephane Rolland
但我同意你的观点,只要它能够完成一个精确的任务。 - Stephane Rolland

3
首先,我会将成员分为两组:(1) 仅限内部使用的成员,(2) 用户将调整以控制类行为的成员。第一组应该只是私有成员变量。
如果第二组很大(或者因为您仍在进行活动开发而在增长和更改),那么您可能会将它们放入一个自己的类或结构中。您的主要类将具有两种方法,即GetTrackingParametersSetTrackingParameters。构造函数将建立默认值。然后用户可以调用GetTrackingParameters,进行更改,然后调用SetTrackingParameters。现在,当您添加或删除参数时,您的接口保持不变。
如果参数简单且正交,则可以将它们包装在具有良好命名的公共成员的结构中。如果必须强制执行约束,特别是组合,则会为每个参数实现getter和setter的类来实现参数。
ObjectTracker tracker;  // invokes constructor which gets default params
TrackerParams params = tracker.GetTrackingParameters();
params.number_of_objects_to_track = 3;
params.other_tracking_option = kHighestPrecision;
tracker.SetTrackingParameters(params);
// Now start tracking.

如果您后来发明了一个新的参数,您只需要在TrackerParams中声明一个新成员,并在ObjectTracker的构造函数中初始化它。

如果我可以进行一些死灵术:您在这种情况下所说的“正交”是指一个参数不会影响任何其他参数吗?而在“简单和正交”参数的情况下,“包装在结构体中”--> 这个结构体仍然可以通过Get/SetParams函数访问,还是这个结构体将成为ObjectTracker类的公共成员? - LCsa
@LCsa:是的,“正交”可能不是最好的词;“独立”可能更好。如果您可以将任何参数更改为任何值而无需调整任何其他参数,则没有必要使用getter和setter,因为没有约束或不变量需要强制执行。通过“包装在结构体中”,我指的是一个简单的C风格结构体,只有数据成员,除了默认构造函数之外没有其他方法。 - Adrian McCarthy
谢谢!我找到了这个线程,非常有用,但是我仍在思考如何设计类而无需使用无数的getter和setter... - LCsa

3
如果我必须从你列出的四个解决方案中选择一个,那么我会选择:在类内部创建私有类。
实际上,你可能有一些重复的代码需要被重用,你应该将你的类重新组织成更小、更合乎逻辑和可重用的部分。正如GMan所说:重构你的代码。

2

一切都取决于:

  1. 如果您需要组织非常多的项目,则内部结构只有在这种情况下才有用。如果是这种情况,您应该重新考虑设计。
  2. 如果将与同一或不同类的其他实例共享,则外部结构将很有用。 (模型或数据对象类/结构可能是一个很好的例子)
  3. 仅适用于琐碎的、一次性的代码。
  4. 这是标准的做法,但一切都取决于您将如何使用类。

我理解得对吗,(2) 实际上是 Adrian McCarthy 回答中的 TrackerParams 类,但是定义在 ObjectTracker 之外? - LCsa

1
听起来这可能需要使用模板,就像你描述的那样。
template class FunctionOptimizer <typename FUNCTION, typename METHOD, 
    typename PARAMS>

例如,其中PARAMS封装了简单的优化运行参数(迭代次数等),而METHOD包含实际的优化代码。FUNCTION描述了您要针对优化的基本函数。
关键不在于这是“最佳”方法,而是如果您的类非常大,则其中很可能存在较小的抽象,自然地适合重构为不那么庞大的结构。
无论您如何处理,都不必一次性进行重构-从小处开始逐步进行,并确保代码在每个步骤中都能正常工作。您会惊讶地发现,您对代码的感觉会更好得多。

0

我认为单独创建一个结构体来保存参数没有任何好处。类已经是一个结构体了 - 如果通过结构体传递参数是合适的,那么将类成员公开也是合适的。

公共成员和Set/Get函数之间存在权衡。公共成员要少得多,但它们会暴露类的内部工作原理。如果这将从您无法重构类的代码中调用,则几乎肯定需要使用Get和Set。


我有一个名为GraphicsVertex的类,其中有5个成员变量(指针)。当我的应用程序运行时,它会创建大约5000万个对象。现在对于每个对象,它将占用5个成员* 5000万内存。我该如何避免这种情况? - DigviJay Patil
@DigviJayPatil 我不知道是否有任何避免它的方法。而且这完全是错误的提问方式。 - Mark Ransom
我应该提出问题还是在评论中问? - DigviJay Patil
@DigviJayPatil 如果你有问题,应该直接提问,并提供更多细节。你也应该删除你在这里的评论。 - Mark Ransom

0

假设配置选项仅适用于此类,请使用由具有有意义的函数名称的公共函数操作的私有变量。 SetMaxInteriorAngle()SetMIA()SetParameter6()好得多。拥有getter和setter可以让您强制执行配置的一致性规则,并可用于补偿配置接口中某些数量的更改。

如果这些是通用设置,被多个类使用,则最好使用外部类,具有私有成员和适当的函数。

公共数据成员通常不是一个好主意,因为它们暴露了类的实现,并使其不可能有任何保证的关系。将它们隔离在单独的内部结构中似乎没有用处,尽管我会将它们分组在数据成员列表中,并用注释标出。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接