c++constructordefault-constructormember-initialization

Member of struct constructed twice in custom constructor?


struct PhiBlock
{   
    int64_t bsize;              // block size
    vector<int64_t> ind;        // 0/1 to track [pmin(y) > pb]
    fenwick_tree phi_sum;       // partial sums

    PhiBlock (int64_t bsize, int64_t a):
        phi_sum(ind)
    {
        this->bsize = bsize;
        ind.resize(bsize, 1);
        phi_sum = fenwick_tree(ind);
    }
    // ...

My data structure:

struct fenwick_tree
{
    size_t len; // 0-based len
    std::vector<int64_t> t; // 1-based tree, indexes [1:len]

    fenwick_tree(std::vector<int64_t> const &a)
    {
        len = a.size();
        t.assign(len + 1, 0);
        for (size_t i = 0; i < len; ++i)
            add_to(i, a[i]);
    }

    // ...
};

If I don't include the member initialization list for PhiBlock, GCC complains about no matching function call to fenwick_tree::fenwick_tree(). So, here's what I think is happening:

  1. fenwick_tree has no default constructor, so I have to use member initialization phi_sum(ind), where ind is default constructed as a vector to be empty.

  2. ind is resized.

  3. A new fenwick_tree is constructed using ind, then phi_sum is set to that with however operator= is implemented.

Is this correct? Must phi_sum be constructed twice? If I modeled my fenwick_tree off of the standard library containers, then it should have a default constructor that does nothing - then I wouldn't need a member initialization list, right?

Update: Here is the gcc output https://godbolt.org/z/5Y7Y9o15W

It's hard for me to see what's going on, but as far as I can tell based on the calls:

Without -O2, fenwick_tree is clearly getting constructed twice. I would guess with -O2 fenwick_tree is not constructed twice.


Solution

  • Your understanding is basically correct. Since fenwick_tree does not have a default constructor, the phi_sum member MUST be constructed using the member initialization list of PhiBlock's constructor.

    However, class members are initialized in the order that they are declared in the class (regardless of the order that they appear in the constructor's member initialization list). And, any non-defaulted member can be (and should be) initialized in the member initialization list.

    So, because the ind member is declared before the phi_sum member, you can initialize ind first via the member initialization list, using the vector::vector(size_type count, const T& value) constructor. And then, you can initialize phi_sum afterwards using the now-populated ind member, eg:

    struct PhiBlock
    {   
        int64_t bsize;              // block size
        vector<int64_t> ind;        // 0/1 to track [pmin(y) > pb]
        fenwick_tree phi_sum;       // partial sums
    
        PhiBlock (int64_t bsize, int64_t a):
            bsize(bsize), ind(bsize, 1), phi_sum(ind)
        {
        }
    
        // ...
    };