Arguments against "initialize()" method instead of constructors
Asked Answered
R

7

10

I'm currently in charge of finding all bad practices in our code base and to convince my colleagues to fix the offending code. During my spelunking, I noticed that many people here use the following pattern:

class Foo
{
  public:
    Foo() { /* Do nothing here */ }
    bool initialize() { /* Do all the initialization stuff and return true on success. */ }
    ~Foo() { /* Do all the cleanup */ }
};

Now I might be wrong, but to me this initialize() method thing is awful. I believe it cancels the whole purpose of having constructors.

When I ask my collegues why this design decision was made, they always answer that they have no choice because you can't exit a constructor without throwing (I guess they assume throwing is always bad).

I failed to convince them so far and I admit I may lack of valuable arguments... so here is my question: Am I right that this construct is a pain and if so, what issues do you see in it ?

Thank you.

Rigadoon answered 19/3, 2012 at 9:32 Comment(6)
FTR, this is known as two-phase initialization.Asphodel
here's an obvious question: if construction fails, what do your coworkers want to do with the halfway-constructed object? It's useless. It might as well have thrown an exception on constructionDisembark
YPB! I've been in that position and it is like herding cats.Brathwaite
I just found this duplicate: Should I use virtual 'Initialize()' functions to initialize an object of my class?Rigadoon
Similar question with some good answers: Why use an initialization method instead of a constructor?Pym
See https://mcmap.net/q/298753/-why-use-an-initialization-method-instead-of-a-constructorBelie
M
7

Both single step (constructor) initialisation and two step (with an init method) initialisation are useful patterns. Personally I feel that excluding either is a mistake, although if your conventions prohibit use of exceptions entirely then you prohibit single step initialisation for constructors that can fail.

In general I prefer single step initialisation because this means that your objects can have stronger invariants. I only use two step initialisation when I consider it meaningful or useful for an object to be able to exist in an "uninitialised" state.

With two step initialisation it is valid for your object to be in an uninitialised state - so every method that works with the object needs to be aware of and correctly handle the fact that it might be in an uninitialised state. This is analogous to working with pointers, where it is poor form to assume that a pointer is not NULL. Conversely, if you do all your initialisation in your constructor and fail with exceptions than you can add 'the object is always initialised' to your list of invariants, and so it becomes easier and safer to make assumptions about the state of the object.

Magnetograph answered 19/3, 2012 at 9:42 Comment(0)
E
6

This is usually known as Two phase or Multiphase Initialization and it is particularly bad because once a constructor call has finished successfully, you should have a ready to use object, In this case you won't have a ready-to-use object.

I cannot help but stress more on the following:
Throwing an exception from constructor in case of failure is the best and the only concise way of handling object construction failures.

Eisenach answered 19/3, 2012 at 9:37 Comment(0)
T
1

It depends on the semantics of your object. If the initialization is something that's crucial to the data structure of the class itself, then a failure would be better handled by either throwing an exception from the constructor (e.g. if you are out of memory), or by an assertion (if you know that your code should not actually fail, ever).

On the other hand, if success or otherwise of the construction depends on user input, then failure is not an exceptional condition, but rather part of the normal, expected runtime behaviour that you need to test for. In that case, you should have a default constructor that creates an object in an "invalid" state, and an initialization function that can be called either in a constructor or later and that may or may not succeed. Take std::ifstream as an example.

So a skeleton of your class could look like this:

class Foo
{
    bool valid;
    bool initialize(Args... args) { /* ... */ }

public:
    Foo() : valid(false) { }
    Foo(Args... args) : valid (false) { valid = initialize(args...); }

    bool reset(Args... args)   // atomic, doesn't change *this on failure
    {
        Foo other(args...);
        if (other) { using std::swap; swap(*this, other); return true; }
        return false;
    }

    explicit operator bool() const { return valid; }
};
Tent answered 19/3, 2012 at 9:50 Comment(0)
P
0

It depends on the case.

If a constructor can fail because of some arguments, an exception should be thrown. But, of course, you need to document yourself on throwing exceptions from constructors.

If Foo contains objects, they will be initialized twice, once in the constructor, once in the initialize method, so that's a drawback.

IMO, the biggest drawback is that you have to remember to call initialize. What's the point of creating an object if it's invalid?

So, if their only argument is that they don't want to throw exceptions from the constructor, it's a pretty bad argument.

If, however, they want some sort of lazy initialization, it's valid.

Pianism answered 19/3, 2012 at 9:37 Comment(0)
A
0

It is a pain, but you have no other choice if you want to avoid throwing exception from constructor. There is also another option, equally painful: do all initialization in the constructor and then you have to check if the object has been constructed succefully (e.g. conversion operator to bool or IsOK method). Life is hard, ..... and then you die :(

Autochthon answered 19/3, 2012 at 9:38 Comment(6)
But if an object could not be initialized (and thus, could not be constructed), what uses does it have anyway ? To me the IsOK method thing is equally wrong.Rigadoon
The idea is that in the constructor you only set all members to initial values (so all of them are in defined state), then you invoke initialize which can (potentially) throw an exception. Just after the constructor the IsOK (or operator bool) returns false. The object always can be initialized to some initial (known) values. I'm not arguing it is a good idea, just sometimes it is the only one sane alternative. BTW: if you want to catch an exception thrown from constructor in theinitialization list - then the syntax becomes really ugly - from both evils I would choose the IsOK/oper_bool.Autochthon
But what does the object represent after it was constructed but before I call initialize() ?Rigadoon
It represents an empty object, but as I wrote above all of its members are in defined state (e.g. all pointers set to NULL, etc.). Even when you debugging such stuff you can see clearly what has been defined and what is not. Another argument could be lazy initialization as mentioned below by Luchian Grigore.Autochthon
Indeed. It may be useful in some cases then. But what about cases where an "empty" object doesn't make sense ?Rigadoon
Of course it is possible that the empty object does not make sense, but it is your code and your decisions. It is your code that creates objects, so you can always check if everything is OK or not - if it is not OK, then you are free to forbid using the object or throw exception from more convenient place (e.g. not from constructor). Even if the empty object is used it does nothing, but its usage is safe in the sense that it will not crash, because it is (partially) udefined.Autochthon
M
0

I realise this is a very old thread, but I wanted to add something that hasn't been explicitly stated (or is perhaps simply implied). In C++, when a constructor throws an exception, the object is not considered "constructed" and therefore its destructor will not get called as part of the exception unwinding.

This can be a very real motivating factor for having an initialise( ) method instead of doing it in the constructor. A complex object doing lots of memory allocation and the like would have to unwind all that work manually if the constructor threw an exception.

If an initialise( ) method is used, the object is already "constructed" at the time of initialisation, and hence the object's destructor will be called.

So, yes, doing the initialisation in the constructor is "nicer", but it also puts a greater burden on the programmer to clean up properly if things go wrong. A piecemeal approach to cleaning up will make for very ugly code.

In some cases, therefore, it might be better to accept pragmatism over idealism.

Mechanic answered 26/6, 2019 at 1:30 Comment(0)
M
0

When an object is constructed, it must be ready to use. most SDK developers forgot this rule, and they expect to call another method named initialize to complete setup because they have to do some async tasks to construct object, but problem is constructors can't be async.

the solution is you should set constructor private, so no one can initialize the object. then add an static method which returns an instance of the object asynchronously.

here is an example in dart:

class FacetecPlugin {
  final String _token;
  // Ensures end-users cannot initialize the class.
  FacetecPlugin._privateConstructor({required String token}) : _token = token;

  Future<void> checkLiveness() async {
    await FacetecPlatform.instance.checkLiveness(_token);
  }

  //This is method should be called before any usage of FacetecPlugin plugin.
  static Future<FacetecPlugin> initialize(Configuration config) async {
    final initialized = await FacetecPlatform.instance.initialize(
        deviceKeyIdentifier: config.deviceKey,
        publicEncryptionKey: config.publicEncryptionKey);
    if (!initialized) {
      throw Exception('Facetec SDK initialization failed.');
    }
    final token = await _getSessionToken(config);
    // creating an instance of the plugin with token and returning it.
    return FacetecPlugin._privateConstructor(token: token);
  }

# other methods have been omitted for brevity.

Now user can't construct the plugin directly. but he has to use initialize method which return a properly constructed plugin and is ready to use.

// final facetecPlugin = FacetecPlugin() // can't be called. constructor is private
final facetecPlugin = await FacetecPlugin.initialize(Configuration(deviceId:'1234'));
final result = await facetecPlugin.checkLiveness()
Moscow answered 3/8, 2022 at 6:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.