14

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.

linuxeasy
  • 6,269
  • 7
  • 33
  • 40
  • 3
    `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. – juanchopanza Jul 11 '14 at 16:34
  • 1
    Use a pointer. I Java it's all pointers, that confuses you. – Peter - Reinstate Monica Jul 11 '14 at 16:36
  • @juanchopanza so you recommend going with this way: `Second() : first(0) { first = First(123); }` – linuxeasy Jul 11 '14 at 16:37
  • 5
    It's possible to do the calculations and use them to initialize `first` in the parameter list still. Use a function. – Mooing Duck Jul 11 '14 at 16:37
  • @PeterSchneider True, but I want it use it as least as possible. To save myself bothering about destructor,assignment-operator and copy-constructor – linuxeasy Jul 11 '14 at 16:38
  • 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 type – PurityLake Jul 11 '14 at 16:39
  • 1
    So use an appropriate smart pointer type, either `unique_ptr` or `shared_ptr`, depending on what d-tor, assignment, and copy should do. – Phil Miller Jul 11 '14 at 16:39
  • This is _no_ place for a pointer (smart or otherwise). – James Kanze Jul 11 '14 at 18:01

6 Answers6

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.
Neonit
  • 680
  • 8
  • 27
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 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? – linuxeasy Jul 11 '14 at 17:08
  • 2
    @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. – Mooing Duck Jul 11 '14 at 17:15
  • 1
    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). – James Kanze Jul 11 '14 at 17:59
  • Sadly it's C++20 now and we still don't have a better way to write a constructor. – RnMss Jan 31 '21 at 17:46
  • @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. – Mooing Duck Feb 01 '21 at 16:52
  • @MooingDuck It will be a disaster initializing non move/copy constructible members. – RnMss Feb 02 '21 at 18:07
  • And yet somehow it's worked for every program in every language so far.... – Mooing Duck Feb 02 '21 at 18:29
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;
};
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 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? – linuxeasy Jul 11 '14 at 17:06
  • @linuxeasy 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`. – ecatmur Jul 11 '14 at 17:11
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.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
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

DTSCode
  • 1,062
  • 9
  • 24
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.

Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • 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() – DTSCode Jul 11 '14 at 16:41
  • 1
    @DTSCode 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? – Paul Evans Jul 11 '14 at 16:45
  • @DTSCode: Just deleting it in the destructor would give the class invalid copy semantics. You'll need [more work](http://stackoverflow.com/questions/4172722) or a smart pointer (or one of the better suggestions in other answers) to fix that. – Mike Seymour Jul 11 '14 at 16:45
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.

Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270