Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialization block to be run after constructors

Let's say I have the below class:

template <class Base>
struct Wrapper : public Base {
    using Base::Base;
    
    // ... add functionality ...
};

And I want some code to be executed during construction after constructors. I can't add a default constructor, because it won't be run when an inherited constructor is used. One idea is this:

template <class Base>
struct Wrapper : public Base {
    bool _ = [this] {
        // initialize ... 
        return true;
    }();

    using Base::Base;
};

This does work perfectly well as long as you place it as the last member, but it wastes memory.

Another way is:

#include <type_traits>

template <class T>
struct InitBlock {
    InitBlock() {
        static_cast<T*>(this)->init();
    }
};

template <class Base>
struct Wrapper : public Base, private InitBlock<Wrapper<Base>> {
private:
    template <class T>
    friend struct InitBlock;

    void init() {
        // initialize ...
    }

public:
    using Base::Base;
};

Which is good, but a bit verbose. And there is nothing protecting init from being called again elsewhere. Also, if Wrapper adds members, this is called before those are initialized, so it's not ideal.

What is a better (safe and low on boilerplate) way of doing this?

like image 480
Ayxan Haqverdili Avatar asked Jun 02 '26 19:06

Ayxan Haqverdili


1 Answers

As mentioned, we can use [[no_unique_address]] to avoid allocating memory for an empty class. It is honored by all major compilers, except for MSVC. MSVC has its own extension of [[msvc::no_unique_address]]. We wrap a macro around this, and it works fine:

#include <stdio.h>

#ifdef _MSC_VER
#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#else 
#define NO_UNIQUE_ADDRESS [[no_unique_address]]
#endif

template <class Base>
struct Wrapper : public Base {
private:
    NO_UNIQUE_ADDRESS struct Init {} _ = [] {
        puts("Wrapper init");
        return Init{};
    }();
public:
    using Base::Base;
};


struct Foo {
    int i;

    Foo(int i) : i(i) {
        printf("Foo(%d)\n", i);
    }
};

int main() {
    Wrapper<Foo> wfoo(42);
    Foo foo(43);
    static_assert(sizeof(wfoo) == sizeof(foo));
}

You still have to take care to place it after all other members to make sure they're initialized by the time we touch them, so it's not ideal.

See online

like image 55
Ayxan Haqverdili Avatar answered Jun 05 '26 08:06

Ayxan Haqverdili



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!