代码之家  ›  专栏  ›  技术社区  ›  wadesworld

与硬件接口的C++构造函数是否应该做真正的工作?[复制品]

  •  29
  • wadesworld  · 技术社区  · 14 年前

    可能重复:
    How much work should be done in a constructor?

    我脑子里有一些建议,但我记不清是怎么回事。

    我似乎记得在某个时候读一些建议(不记得来源),C++构造函数不应该做真正的工作。相反,它们应该只初始化变量。建议继续解释说,真正的工作应该在某种init()方法中完成,该方法在创建实例后单独调用。

    情况是我有一个表示硬件设备的类。对于我来说,构造函数调用查询设备的例程以构建描述设备的实例变量是合乎逻辑的。换句话说,一旦new实例化了对象,开发人员就会收到一个准备好使用的对象,不需要单独调用object->init()。

    建造师不应该做真正的工作有充分的理由吗?显然,它可以降低分配时间,但如果在分配之后立即调用单独的方法,这也不会有任何不同。

    只是想弄清楚我目前没有考虑到的可能导致这种建议的原因。

    10 回复  |  直到 6 年前
        1
  •  26
  •   Extrakun    14 年前

    我记得Scott Meyers在更有效的C++中建议不要使用一个多余的默认构造函数。在那篇文章中,他还提到了使用init()这样的方法来“创建”对象。基本上,您已经引入了一个额外的步骤,将责任放在类的客户机上。此外,如果要创建所述对象的数组,则每个对象都必须手动调用init()。您可以有一个init函数,构造函数可以调用该函数来保持代码整洁,或者如果实现reset()则调用该对象,但是根据经验,最好删除一个对象并重新创建它,而不是尝试将其值重置为默认值,除非对象是实时创建和销毁的(例如,粒子efFECTES)。

    另外,请注意,构造函数可以执行常规函数不能执行的初始化列表。

    人们可能会警告不要使用构造函数进行大量资源分配的一个原因是,很难在构造函数中捕获异常。但是,有很多方法可以解决这个问题。否则,我认为构造函数应该做他们应该做的事情——为对象的初始执行状态做准备(对于对象创建来说,重要的是资源分配)。

        2
  •  18
  •   Warpin    14 年前

    不在构造函数中执行“工作”的一个原因是,如果从构造函数中抛出异常,则不会调用类析构函数。但是如果你使用RAII原理,不依赖于你的析构函数来做清理工作,那么我觉得最好不要引入一种不需要的方法。

        3
  •  9
  •   paxdiablo    14 年前

    取决于你所说的真正的工作。构造器应该将对象置于可用状态,即使该状态是一个标志,意味着它尚未初始化:—)

    我所遇到的不做实际工作的唯一理由是,构造函数失败的唯一方法是出现异常(在这种情况下不会调用析构函数)。没有机会返回一个好的错误代码。

    你必须问自己的问题是:

    对象是否可用而不调用 init 方法?

    如果答案是“否”,那么我将在构造函数中完成所有工作。否则,您必须捕获用户已实例化但尚未初始化的情况,并返回某种类型的错误。

    当然,如果可以的话 重新初始化 设备,你应该提供一些 初始化 方法,但在这种情况下,我会 仍然 如果满足上述条件,则从构造函数调用该方法。

        4
  •  7
  •   Neal Stublen    14 年前

    除了有关异常处理的其他建议外,连接到硬件设备时要考虑的一件事是类如何处理设备不存在或通信失败的情况。

    在无法与设备通信的情况下,您可能需要在类上提供一些方法,以便稍后执行初始化。在这种情况下,只实例化对象然后运行初始化调用可能更有意义。如果初始化失败,您可以保留对象,稍后再尝试初始化通信。或者您可能需要处理初始化后通信丢失的情况。在这两种情况下,您可能都想考虑如何设计类来处理一般的通信问题,这可能有助于您决定在构造函数和初始化方法中要做什么。

    当我实现了与外部硬件通信的类后,我发现实例化一个“断开连接”的对象并提供连接和设置初始状态的方法更容易。这通常为与设备的连接/断开/重新连接提供了更大的灵活性。

        5
  •  3
  •   Alex R    14 年前

    唯一的真正原因是可测试性。如果您的构造函数充满了“实际工作”,这通常意味着对象只能在完全初始化、正在运行的应用程序中实例化。这是对象/类需要进一步分解的标志。

        6
  •  3
  •   daramarak    14 年前

    当使用构造函数和init()方法时,您有一个错误源。根据我的经验,你会遇到这样的情况:有人忘记给它打电话,而你的手上可能有一个小虫子。我会说,您不应该在构造函数中做太多工作,但是如果需要任何in i t方法,那么您就有了一个非常重要的构建场景,现在是时候看看创建模式了。一个建设者的职能或工厂是明智的看一看。使用一个私有的构造函数,确保除了工厂或生成器函数之外没有其他人实际构建对象,因此可以确保对象始终是正确构建的。

    如果您的设计允许在实现中出现错误,那么有人会犯这些错误。我的朋友墨菲告诉我;()

    在我的领域中,我们处理类似硬件相关情况的负载。工厂为我们提供了可测试性、安全性和更好的失败构建方法。

        7
  •  2
  •   Michael    14 年前

    Neal S.指出,值得考虑终身问题和连接/重新连接。

    如果在链接的另一端无法连接到某个设备,那么通常情况下,您端的“设备”是可用的,如果另一端将其行为组合在一起,则稍后将可用。例如网络连接等。

    另一方面,如果您尝试访问一些不存在并且永远不会存在于程序范围内的本地硬件设备(例如不存在的图形卡),那么我认为这是一个您希望在构造函数中知道这一点的情况,以便构造函数可以抛出而对象不存在。如果您不这样做,那么您可能最终会得到一个无效的对象,并且总是这样。传入构造函数意味着对象将不存在,因此不能对该对象调用函数。显然,如果您抛出一个构造函数,您需要注意清理问题,但是如果您在这种情况下不这样做,那么您通常会在所有可能被调用的函数中进行验证检查。

    所以我认为您应该在构造函数中做足够的工作,以确保创建了一个有效的、可用的对象。

        8
  •  1
  •   Matthieu M.    14 年前

    我想在那里增加我自己的经验。

    我不会说太多关于传统的辩论构造器/初始化…例如 Google 指导方针建议不要使用构造函数中的任何内容,但这是因为它们建议不要使用异常,而这两个原则共同工作。

    我可以说一个 Connection 不过我用的是类。

    连接 类已创建,它将 尝试 实际连接自己(至少,如果不是默认构造的话)。如果 连接 失败。。。对象仍然是构造的,您不知道。

    当你试图使用 连接 因此,在以下三种情况中,您属于一类:

    • 从未精确定义过任何参数>异常或错误代码
    • 对象实际上已连接>很好
    • 对象未连接,它将尝试连接>此操作成功,很好,此操作失败,您将收到异常或错误代码。

    我认为两者兼得很有用。但是,这意味着在实际使用连接的每个方法中,您都需要测试它是否工作。

    但这是值得的,因为断线事件。当您连接时,您可能会在对象不知道的情况下丢失连接。通过将连接自检封装到 reconnect 方法是由需要工作连接的所有方法在内部调用的,您确实将开发人员与处理问题隔离开来…或者至少尽你所能,因为当一切都失败了,你没有其他的解决办法让他们知道:)

        9
  •  0
  •   ardsrk    14 年前

    最好避免在构造函数中执行“实际工作”。

    如果我在一个构造函数内设置数据库连接、打开文件等,并且这样做时其中一个会引发异常,那么这将导致内存泄漏。这会损害您的应用程序 exception safety .

    避免在构造函数中进行工作的另一个原因是它会降低应用程序的可测试性。假设您正在编写一个信用卡支付处理器。如果说 CreditCardProcessor 类的构造函数您完成连接到支付网关的所有工作,授权并为信用卡记帐我如何编写单元测试 信用卡处理器 班级?

    在您的场景中,如果查询设备的例程没有引发任何异常,并且您不打算单独测试类,那么最好在构造函数中进行工作并避免调用额外的 init 方法。

        10
  •  0
  •   Ioan    14 年前

    我使用单独的构造函数/init()有几个原因:

    • 延迟/延迟初始化。这允许您快速、快速地创建对象,并延迟较长的初始化时间,以便稍后或后台处理。这也是有关可重用对象池的一个或多个设计模式的一部分,以避免昂贵的分配。
    • 不确定名称是否正确,但可能在创建对象时,初始化信息不可用,或者创建对象的任何人都无法理解(例如,批量通用对象创建)。代码的另一部分有初始化它的诀窍,但没有创建它。
    • 作为个人原因,析构函数应该能够撤消构造函数所做的一切。如果这涉及到使用内部init/deinit(),没问题,只要它们是彼此的镜像。