在不允许异常的环境中(例如在谷歌中),C++构造函数必须有效地成功,因为它们无法向调用方报告失败。当然,您可以使用abort(),但是这样做会使整个程序崩溃,这在生产代码中通常是不可接受的。

如果类的初始化逻辑无法避免失败的可能性,一种常见的方法是给类一个初始值设定项方法(也称为“init方法”),该方法执行任何可能失败的初始化工作,并通过其返回值通知失败。通常假定用户在构造后立即调用此方法,如果失败,则用户将立即销毁该对象。然而,这些假设并不总是记录在案,也不总是遵守。在初始化之前或初始化失败之后,用户很容易开始调用其他方法。有时,类实际上鼓励这种行为,例如,通过提供方法在初始化对象之前对其进行配置,或者在初始化失败后从中读取错误。

这种设计要求您维护一个类,该类至少具有两种不同的用户可见状态,通常有三种状态:初始化、未初始化和初始化失败。使这样的设计工作需要很多规则:类的每个方法都必须指定可以调用它的状态,并且用户必须遵守这些规则。如果这一原则失效,不管您打算支持什么,客户机开发人员都会倾向于编写任何代码。当这种情况开始发生时,可维护性就开始下降,因为您的实现必须支持客户机开始依赖的任何预初始化方法调用组合。实际上,您的实现已经成为您的接口。

幸运的是,有一个简单的替代方法缺少这些缺点:提供一个工厂函数,它创建并初始化类的实例,并通过指针或absl::optional(请参见TotW #123)返回它们,使用null表示失败。以下是一个使用unique_ptr<>例子:

// foo.h
class Foo {
 public:
  // Factory method: creates and returns a Foo.
  // May return null on failure.
  static std::unique_ptr<Foo> Create();

  // Foo is not copyable.
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;

 private:
  // Clients can't invoke the constructor directly.
  Foo();
};

// foo.c
std::unique_ptr<Foo> Foo::Create() {
  // Note that since Foo's constructor is private, we have to use new.
  return absl::WrapUnique(new Foo());
}

 在许多情况下,此模式为您提供了两个方面的最佳选择:工厂函数foo::create()只公开完全初始化的对象(如构造函数),但它可以指示类似初始值设定项方法的失败。工厂函数的另一个优点是,它们可以返回返回类型的任何子类的实例(尽管如果使用absl::optional作为返回类型,则不可能这样做)。这允许您在不更新用户代码的情况下交换不同的实现,甚至可以基于用户input动态选择实现类。

这种方法的主要缺点是它返回一个指向堆分配对象的指针,因此它不太适合设计用于堆栈的“类值”类。然而,这样的类首先通常不需要复杂的初始化。当派生类构造函数需要初始化其基时,工厂函数也不能使用,因此在基类的受保护API中有时需要初始值设定项方法。不过,公共API仍然可以使用工厂函数。