Is there a way to late-initialize a member variable (a class) in C++?
Asked Answered
J

6

15

I am coming from the Java background. I have the following program.

#include <string>
#include <iostream>

class First {
    public:
    First(int someVal): a(someVal) {

    }
    int a;
};

class Second {
    public:
    First first;
    Second()  {   // The other option would be to add default value as ": first(0)"
        first = First(123);

    }
};

int main()
{
    Second second;
    std::cout << "hello" << second.first.a << std::endl;
}

In class Second, I wanted to variable first to remain uninitialized until I specifically initialize it in Second()'s constructor. Is there a way to do it? Or am I just left with 2 options?:

  1. Provide a parameter-less constructor.
  2. Initialize it with some default value and later re-assign the required value.

I can't initialize first in the initializer-list with the right value, since the value is obtained after some operation. So, the actual required value for first is available in Second() constructor only.

Jennette answered 11/7, 2014 at 16:32 Comment(8)
first will be initialized as soon as you instantiate a Second. There's no way around that. All you can do is change its value at a later stage.Gird
Use a pointer. I Java it's all pointers, that confuses you.Mauriac
@Gird so you recommend going with this way: Second() : first(0) { first = First(123); }Jennette
It's possible to do the calculations and use them to initialize first in the parameter list still. Use a function.Devour
@PeterSchneider True, but I want it use it as least as possible. To save myself bothering about destructor,assignment-operator and copy-constructorJennette
I fail to see what you want to do. You don't want to initialize first yet in Second's constructor you are initializing it anyway? Set first to be a pointer and set it to NULL or nullptr for C++11 in your constructor. Then at a later stage use new First(arg) to create a new pointer to an object of First typeArianism
So use an appropriate smart pointer type, either unique_ptr or shared_ptr, depending on what d-tor, assignment, and copy should do.Fluorocarbon
This is no place for a pointer (smart or otherwise).Lumbricoid
D
12

MY suggestion: Use a function:

private: static int calculate_first(int input) {return input*5;}
explicit Second(int input) : first(calculate_first(input)) {}

Base classes will be initialized in the order they're declared in the class inheritance list, and then members will be initialized in the order that they're listed in the class, so the calculation can depend on non-static member-variables and base classes if they have already been initialized.


Alternatively:

Default constructor, then reassign:

explicit Second(int input) { first = input*5; }

Dummy value, then reassign:

explicit Second(int input) : first(0) { first = input*5; }

Use boost::optional (or std::optional as of C++17):

boost::optional<First> first;
explicit Second(int input) { first = input*5; }

Use the heap:

std::unique_ptr<First> first;
explicit Second(int input) { first.reset(new First(input*5));}
Second(const Second& r) first(new First(*(r->first))) {}
Second& operator=(const Second& r) {first.reset(new First(*(r->first)));}

Placement new:

This is tricky and not suggested 
and worse in every way than boost::optional
So sample deliberately missing.
But it is an option.
Devour answered 11/7, 2014 at 16:42 Comment(7)
About the static calculate_first function mentioned in the first suggestion, this isn't going to work if the calculations depend on non-static member-variables or member-variables of base-class right?Jennette
@linuxeasy: Base classes will be initialized in the order they're declared in the class inheritance list, and then members will be initialized in the order that they're listed in the class, so the calculation can depend on non-static member-variables and base classes if they have already been initialized.Devour
Practically, all but the first two are to be avoided. On the other hand... I've occasionally found it useful to provide a "simplified" constructor for something like his First, which takes an explicit, dummy parameter to tell the constructor to do nothing (or as little as possible for a later assign to work).Lumbricoid
Sadly it's C++20 now and we still don't have a better way to write a constructor.Lingual
@RnMss: And it never will. The fundamental problem is how composition works. These are the theoretical solutions. It's possible I'm missing something, but this is never going to get "better". The Java solution is merely syntactical sugar for the heap variant.Devour
@MooingDuck It will be a disaster initializing non move/copy constructible members.Lingual
And yet somehow it's worked for every program in every language so far....Devour
S
4

Initialize first in the member initializer list.

It may help to perform your calculations in a helper function and use a forwarding constructor:

class Second {
public:
    Second() : Second(helper_function()) {}

private:
    Second(int calc): first(calc) {}
    static int helper_function() { return ...; }

    First first;
};
Syncopation answered 11/7, 2014 at 16:39 Comment(2)
About the static helper function mentioned herein, this isn't going to work if the calculations depend on non-static member-variables or member-variables of base-class right?Jennette
@Jennette no, it won't. You can make it a non-static member function if you're careful about only referring to members defined before first.Syncopation
R
3

This sentence is the core of the problem:

I can't initialize first in the initializer-list with the right value, since the value is obtained after some operation.

You should know that what you want to do here is not perfect programming style in Java, either. Leaving the field with some default value and then assigning it a bit later after some calculations have been done effectively prevents it from being final, and consequently the class from being immutable.

In any case, your goal must be to push those calculations directly into the initialization of the member, using private helper functions (which may be static):

class Second {
private:
    First first;

    static int getInitializationData()
    {
        // complicated calculations go here...
        return result_of_calculations;
    }
public:
    Second() : first(getInitializationData()) {}
};

In my opinion, everything else is just a workaround and will complicate your life in the long run.

Rident answered 11/7, 2014 at 17:23 Comment(0)
N
2

You can just do what you said in the comments, or, you can make first a pointer to First and give it memory whenever you like, although i don't recommend this way

Noreen answered 11/7, 2014 at 16:36 Comment(0)
T
1

One way to separate object lifetimes is to use the heap, make first a pointer and initialize it anytime you like:

class Second {
public:
    First* first;
    Second()  { 
        first = new First(123);

    }
};

of course, you'll probably want to use a smart pointer of some sort rather than a raw pointer.

Tenantry answered 11/7, 2014 at 16:38 Comment(3)
well i dont think a smart pointer is required in this context, especially since its in a class he can just delete it in the dtor()Noreen
@Noreen a std::unique_ptr will do exactly what a delete in the dtor would do without having to write that extra code in the dtor. Where's the benifit of not doing that?Tenantry
@DTSCode: Just deleting it in the destructor would give the class invalid copy semantics. You'll need more work or a smart pointer (or one of the better suggestions in other answers) to fix that.Groves
B
0

If you don't code to explicitly initialize a member variable, the default initializer is used to initialize it.

The draft C++ standard has the following about initialization of base classes and member variables:

12.6 Initialization [class.init]

1 When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in 8.5.

And

12.6.1 Explicit initialization [class.expl.init]

1 An object of class type can be initialized with a parenthesized expression-list, where the expression-list is construed as an argument list for a constructor that is called to initialize the object. Alternatively, a single assignment-expression can be specified as an initializer using the = form of initialization. Either direct-initialization semantics or copy-initialization semantics apply; see 8.5.

Bounteous answered 11/7, 2014 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.