Versus
    32-bit vs 64-bit
    Annotations vs Decorators
    BigQuery vs Bigtable
    Block Storage vs File Storage vs Object Storage
    C vs C++
    Canvas vs SVG
    Constructor vs Init() vs Factory
    Containers vs Virtual Machines (VMs)
    DOM vs Virtual DOM vs Shadow DOM
    DQL vs DDL vs DCL vs DML
    Dagger vs Guice
    Data Mining vs Machine Learning vs Artificial Intelligence vs Data Science
    Flux vs Redux
    GCP API Gateway vs Cloud Endpoint
    GCP Cloud Run vs Cloud Functions vs App Engine
    GCP DataFlow vs Dataproc
    Google Analytics 4 vs Universal Analytics
    Google Internal vs Open Source
    HEIC vs HEIF vs HEVC vs JPEG
    Java vs C++
    Jetty vs Netty
    Kotlin vs Java
    LLVM vs JVM
    Linux vs BSD
    Microcontroller vs Microprocessor vs Computer
    Node.js vs Erlang
    POSIX vs SUS vs LSB
    Pass-by-value vs Pass-by-reference
    Proto2 vs Proto3
    PubSub vs Message Queue
    REST vs SOAP
    React vs Flutter vs Angular
    Rust vs C++
    SLI vs SLO vs SLA
    SRAM vs DRAM
    SSD vs HDD
    Software Engineer vs Site Reliability Engineer
    Spanner vs Bigtable
    Stack based VM vs Register based VM
    Stateless vs Stateful
    Static Site Generation vs Server-side Rendering vs Client-side Rendering
    Strong Consistency vs Eventual Consistency
    Subroutines vs Coroutines vs Generators
    Symlinks vs Hard Links
    Tensorflow vs PyTorch
    Terminal vs Shell
    Vi vs Vim vs gVim vs Neovim
    WAL vs rollback journal
    gtag vs Tag Manager
    stubs vs mocks vs fakes

Constructor vs Init() vs Factory

Updated: 2022-02-05

TL;DR

There are a few ways to create a new object in a object-oriented programming languages.

  • Constuctor: public constructors return fully constructed objects.
  • Init: public trivial constructors return partially constructed objects + separate public Init() method to finish initialization.
  • Factory method: private constructors + public static factory (e.g. Create()) methods that return fully constructed objects.

https://abseil.io/tips/42 suggests "Prefer Factory Functions to Initializer Methods".

Constructors

Cons:

  • they cannot fail except by crashing the entire program or throwing an exception. Not suitable for classes with complex initialization logic that may fail.
  • they cannot safely call virtual methods of the object being constructed: if it calls virtual functions, these calls will not get dispatched to the subclass implementations.
  • cannot take the address of a constructor, so whatever work is done in the constructor cannot easily be handed off to, for example, another thread.

public Init() method

Keep the constructors simple, and place complex or possibly-failing initialization in an Init() method

Pros:

  • derived classes can perform possibly-failing initialization of their base classes.

Cons:

  • the object is in a half-initialized state before Init() has been called, which can lead to confusion about which methods can be called when.
  • it can be difficult to reverse the decision to use this design, because it imposes fewer restrictions on client code.

Conclusion:

Prefer factory functions unless there is some good reason to avoid heap allocation, or if the interface naturally lends itself to two-phase initialization.

Guidelines for using Init() methods:

  • If Init() is only needed so derived classes can initialize their base class, make it protected rather than public, and express the public API in terms of factory functions.
  • If possible, have the constructor leave the object in a well-defined 'empty' or 'zero' state, where all methods work correctly even before initialization.
  • If a 'zero' state is not possible, forbid all methods (or as many as possible) from being called before Init(), enforce that rule, and provide a way for clients to determine if the object is initialized.
  • Pass any initialization data as parameters to Init(), not as constructor parameters; avoid situations where an object may store pending-but-uninitialized data, as this is fertile ground for bugs. This has the useful side effect that Init() may be called multiple times to reset the object to different values.
  • By the same token, avoid "setters", i.e. methods that set or modify the state of the object prior to initialization; in addition to the problem of pending-but-uninitialized data, these create the risk that a fully-initialized object may be invalidated by a setter called after initialization. If an API involving setter calls is more natural than passing the data all at once, consider using the Options class pattern or Builder pattern, which let you put these setter methods in a separate class.
  • Failed initialization should leave the object unmodified; don't have separate states for "uninitialized" and "initialization failed". -->

Factory method

How:

  • static Create() function that returns the object (can return null on failure)
  • private constructor so it cannot be invoked directly Not copyable.
  • Never return partially-initialized objects, or objects in an "error" state; factory functions should return a non-null result only on success.
  • Make all constructors private or protected, so that all initialization goes through the factory functions.
  • Try to do the initialization work prior to calling the constructor; it's easier to reason about objects that are fully-initialized as soon as their constructor returns.

Pros:

  • Factory functions can return null to signal failure.
  • Factory functions can choose from different implementations, and may return different concrete implementation classes depending on the parameters
  • Factory functions can have meaningful names, unlike constructors.

Cons:

  • they can only construct objects on the heap, not suitable for lightweight, frequently-allocated, or value-like classes.
  • cannot be invoked from a derived class constructor
// 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:
  Foo(); // Clients can't invoke the constructor directly.
};

Foo foo = Foo::Create();

// foo.cc
std::unique_ptr<Foo> Foo::Create() {
  // Foo's constructor is private, we have to use new. https://abseil.io/tips/134
  return absl::WrapUnique(new Foo());
}