在构造函数中应该做多less工作?

如果操作可能需要一段时间才能在构造函数中执行,或者应该构build对象,然后再进行初始化。

例如,当构造表示目录结构的对象时,应该在构造函数中完成对象及其子项的填充。 显然,目录可以包含目录,而目录又可以包含目录等等。

这个优雅的解决scheme是什么?

从历史上看,我编写了我的构造函数,以便在构造函数方法完成后就可以使用该对象。 代码涉及多less或多less取决于对象的要求。

例如,假设我需要在详细信息视图中显示以下公司类:

public class Company { public int Company_ID { get; set; } public string CompanyName { get; set; } public Address MailingAddress { get; set; } public Phones CompanyPhones { get; set; } public Contact ContactPerson { get; set; } } 

由于我想在细节视图中显示关于公司的所有信息,因此我的构造函数将包含填充每个属性所需的所有代码。 鉴于这是一个复杂的types,公司构造函数也将触发Address,Phones和Contact构造函数的执行。

现在,如果我正在填充一个目录列表视图,在那里我可能只需要公司名称和主要的电话号码,我可能有第二个类的构造函数,只检索信息,并保留剩余的信息为空, 或者我可以创build一个单独的对象,只有这个信息。 这实际上只取决于如何检索信息,以及从哪里。

不pipe一个class上的构造函数的数量多less,我的个人目标是做任何必要的处理,以准备任何可能施加于其上的任务的对象。

总结:

  • 至less,您的构造函数需要将对象configuration为不变式为true的点。

  • 你select的不variables可能会影响你的客户(这个对象是否可以随时准备好访问?或者只在特定的状态?)一个构造函数负责预先设置所有的设置,可以简化生活为class级的客户。

  • 长期运行的构造函数本质上并不坏,但在某些情况下可能不好。

  • 对于涉及用户交互的系统,任何types的长时间运行的方法都可能导致响应性差,应该避免。

  • 延迟计算,直到构造函数可能是有效的优化; 可能会变得没有必要执行所有的工作。 这取决于应用程序,不应该过早确定。

  • 总的来说,这取决于。

你通常不希望构造函数做任何计算。 其他人使用代码不会期望它比基本设置更多。

对于像你所说的目录树,“优雅”的解决scheme可能不是在构build对象时构build完整的树。 相反,按需build立它。 有人使用你的对象可能并不关心子目录中的内容,所以首先让你的构造函数列表第一级,然后如果有人想下降到一个特定的目录,然后build立树的那一部分,当他们请求它。

所需的时间不应成为不把东西放入构造函数的原因。 你可以把代码本身放到一个私有函数中,然后从你的构造函数中调用它,只是为了保持构造函数中的代码清晰。

然而,如果你想要做的事情不需要给对象一个定义的条件,并且你可以在第一次使用时做这些事情,这将是一个合理的理由,把它放出来,并在以后做。 但是不要让它依赖于你的类的用户:这些东西(按需初始化)对于你的类的用户必须是完全透明的。 否则,你的对象的重要不变式可能很容易中断。

这取决于(典型的CS答案)。 如果您在启动时为长时间运行的程序构build对象,那么在构造函数中执行大量工作就没有问题了。 如果这是graphics用户界面(GUI)的一部分,那么可能不太合适。 一如既往,最好的答案就是先尝试最简单的方法,并从中进行优化。

对于这个特定的情况,你可以做一些子目录对象的构造。 只为顶级目录的名称创build条目。 如果他们被访问,然后加载该目录的内容。 当用户展示目录结构时再次执行此操作。

构造函数最重要的工作是给对象一个初始的有效状态。 对我而言,施工人员最重要的期望就是施工人员不应有任何副作用。

为了代码维护,testing和debugging,我尽量避免在构造函数中放置任何逻辑。 如果你更愿意从构造函数中执行逻辑,那么把逻辑放在init()方法中并从构造函数中调用init()会很有帮助。 如果你打算开发unit testing,你应该避免把任何逻辑放在构造函数中,因为可能很难testing不同的情况。 我认为以前的意见已经解决了这个问题,但是…如果你的应用程序是互动的,那么你应该避免有一个单一的调用,导致明显的性能影响。 如果你的应用程序是非交互式的(例如:每晚的批量作业),那么单一的性能下降并不是什么大问题。

我会同意长时间运行的构造函数本身并不坏。 但是我会认为,你几乎总是做错事。 我的build议与Hugo,Rich和Litb类似:

  1. 把你在构造函数中所做的工作保持在最低限度 – 保持专注于初始化状态。
  2. 除非你无法避免,否则不要从施工人员那里扔掉。 我试图只抛出std :: bad_alloc。
  3. 不要调用操作系统或库API,除非你知道他们做什么 – 大多数可以阻止。 他们会很快在你的开箱和testing机器上运行,但在现场,他们可以被长时间阻塞,因为系统忙于做别的事情。
  4. 永远不要在构造函数中做任何I / O操作。 I / O通常受到各种非常长的延迟(100毫秒到几秒)的限制。 I / O包括
    • 磁盘I / O
    • 任何使用networking的(甚至是间接的)记住大部分资源都可以是离线的。

I / O问题的例子:许多硬盘有一个问题,他们进入一个状态,他们没有服务读取或写入100甚至几千毫秒。 第一代和第一代固态硬盘经常这样做。 用户现在已经知道你的程序的问题悬而未决 – 他们只是认为它是你的bug软件。

当然,长时间运行的构造函数的邪恶依赖于两件事情:

  1. “长”意味着什么
  2. 在一个特定的时期里,多长时间一次构build对象。

现在,如果“长”只是几百个额外的时钟周期的工作,那么它不是很长。 但是一个构造函数正在进入100微秒的范围,我认为它是相当长的。 当然,如果你只是实例化其中的一个,或者很less实例化它们(比如每隔几秒),那么由于这个范围的持续时间,你不可能看到问题。

频率是一个重要的因素,如果你只是build立其中的一部分,那么500美元就不是一个问题:但是创造一百万个会造成显着的性能问题。

让我们谈谈你的例子:在“class Directory”对象中填充一个目录对象树。 (注意,我将假设这是一个带有graphics用户界面的程序)。 在这里,你的CTOR持续时间不依赖于你所写的代码 – 它的被告在枚举一个任意大的目录树所花费的时间。 这在本地硬盘上已经够糟糕了。 它在远程(networking)恢复上更成问题。

现在,想象一下,在用户界面线程上这样做 – 您的用户界面将在数秒内,甚至数十秒甚至几分钟内停止。 在Windows中,我们称之为UI挂起。 他们坏坏(是的,我们有他们…是的,我们努​​力消除他们)。

用户界面挂起是可以使人们真正讨厌你的软件的东西。

这里做的正确的事情就是简单地初始化你的目录对象。 在一个可以被取消的循环中构build你的目录树,并保持你的用户界面处于响应状态(取消button应该始终工作)

至于在施工方面应该做多less工作,我想说应该考虑到事情的缓慢程度,你将如何使用这个class级,以及你个人对这个class级的感受。

在你的目录结构对象上:我最近为我的HTPC实现了一个samba(windows共享)浏览器,因为这非常慢,所以我select在被触摸的时候实际初始化一个目录。 例如:首先树会只包含一个机器列表,然后每当你浏览到一个目录时,系统会自动地从这个机器初始化树,并将列出的目录更深一层,依此类推。

理想情况下,我认为你甚至可以把它写成一个工作者线程,它能够广泛地扫描目录,并且会优先考虑你目前正在浏览的目录,但是通常这对于简单的工作来说太复杂了;)

确保ctor不做任何可能抛出exception的东西。

尽可能多的,没有更多。

构造函数必须将对象置于可用状态,因此至less应该启动类variables。 是什么意思可以有一个广泛的解释。 这是一个人为的例子。 想象一下,你有一个class级有责任提供N! 到您的调用应用程序。

实现它的一种方法是让构造函数不做任何事情,用带有循环的成员函数计算所需的值并返回。

另一种实现它的方法是将一个类variables作为一个数组。 构造函数会将所有值设置为-1,以指示该值尚未计算。 成员函数会做懒惰评估。 它看着数组元素。 如果它是-1,则计算它并存储它并返回值,否则它只返回数组中的值。

实现它的另一种方法就像最后一个,只有构造函数会预先计算值,然后填充数组,所以方法可以将值从数组中取出并返回。

实现它的另一种方法是将值保存在文本文件中,并使用N作为文件中偏移的基础来从中提取值。 在这种情况下,构造函数会打开文件,析构函数会closures文件,而方法会做一些fseek / fread并返回值。

实现它的另一种方法是预先计算值,并将它们存储为类可以引用的静态数组。 构造函数没有任何工作,方法将到达数组中获取值并返回。 多个实例将共享该数组。

所有人都说,要关注的是,通常你希望能够调用构造函数一次,然后频繁地使用其他方法。 如果在构造函数中做更多的工作意味着你的方法做的更less,跑得更快,那么这是一个很好的折衷。 如果你正在构造/破坏很多东西,比如在一个循环中,那么为你的构造函数增加成本可能不是一个好主意。

如果可以在构造函数之外进行某些操作,请避免在构造函数内执行。 之后,当你知道你的class上其他人performance不好的时候,你可能会冒这个风险。

RAII是C ++资源pipe理的中坚力量,因此需要在构造函数中获取所需的资源,并在析构函数中释放它们。

这是当你build立你的类不variables。 如果需要时间,则需要时间。 “如果X存在做Y”构造的数量越less,其余的课程devise就越简单。 后来,如果性能分析显示这是一个问题,请考虑优化,如延迟初始化(当您首次需要时获取资源)。

这真的取决于上下文,也就是class级必须解决的问题。 它应该例如总是能够显示当前的孩子在自己的内部? 如果答案是肯定的,那么孩子不应该被加载到构造函数中。 另一方面,如果类表示目录结构的快照,那么它可以在构造函数中加载。

我投票精简构造函数,并在这种情况下添加一个额外的“未初始化”状态行为的对象。

原因是:如果你不这样做,那么你强迫所有的用户也要有沉重的构造函数,或者dynamic地分配你的类。 在这两种情况下,它可能被视为一个麻烦。

如果这些对象变成静态的,可能很难捕获这些对象的错误,因为构造函数在main()之前运行,而debugging器跟踪起来则更加困难。

一个很好的问题:你给“Directory”对象引用其他“Directory”对象的例子也是一个很好的例子。

对于这个特定的情况,我将代码从构造函数中构build出下级对象(或者可以像这里推荐的另一个post那样做第一级[直接子]],并且有一个单独的“初始化”或“构build”机制。

还有另一个潜在的问题 – 除了性能外 – 就是内存占用:如果最终做出非常深的recursion调用,你最终可能也会遇到内存问题[因为堆栈将保留所有局部variables的副本直到recursion结束]。

尝试有你认为是必要的,不要考虑,如果它会缓慢或快速。 Preoptimization是一个时间浪费所以编码,configuration文件和优化,如果需要的话。

对象数组总是使用默认的(无参数)构造函数。 这是没有办法的。

有“特殊的”构造函数:复制构造函数和运算符=()。

你可以有很多的构造函数! 或者稍后结束很多构造函数。 比利时拉比陆地上的比尔需要一个新的浮点数构造函数,而不是双倍存储那些糟糕的字节数。 (买一些RAM帐单!)

您不能像调用普通方法那样调用构造函数来重新调用该初始化逻辑。

您不能使构造函数逻辑虚拟化,并在一个子类中进行更改。 (虽然如果你是从构造函数调用initialize()方法而不是手动的,虚拟方法将不起作用。)

当构造函数中存在重要的逻辑时,所有这些事情都会造成很大的悲痛。 (或至less重复的代码。)

所以我作为一个deviseselect,倾向于使用最小的构造函数(可以根据参数和情况)调用一个initialize()方法。

根据具体情况,initialize()可能是私有的。 或者它可能是公开的,并支持多个调用(例如重新初始化)。

最终,这里的select因情况而异。 我们必须是灵活的,并考虑权衡。 没有一个通用的。

我们用来实现一个单独的实例的方法,这个实例使用线程来与一个专用硬件进行通信,并且必须在1/2小时内写入,而不一定是我们用来实现一个类的东西代表math在数月内写出的浮点精度浮点数。