0

Imagine these classes:

class Base {
public:
    Base() : Base(false)
    { }
    virtual ~Base() = default;

    void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }
protected:
    Base(bool skip_init)
    {
        cout << "Base::Base" << endl;
        if (!skip_init) init();
    }
};

class Derived : public Base {
public:
    Derived() : Base(true)
    {
        cout << "Derived::Derived" << endl;
        init();
    }
    virtual ~Derived() = default;

    void init()
    {
        cout << "Derived::init" << endl;
        Base::init();
        // ...
    }

    virtual void check() override
    {
        cout << "Derived::check" << endl;
        Base::check();
        // ...
    }
};

Then constructing a Derived instance would result in

Base::Base
Derived::Derived
Derived::init
Base::init
Derived::check
Base::check

which is exactly what I want to achieve. It satisfies these requirements:

  • Base defines init() with operations common to all subclasses and should be used right after construction of whole object (and only there)
  • init() can contain virtual functions inside, but since it should be called only in the final constructor, it should not cause any harm
  • check() can be called any time, not only from init() (it should be independent from it), and should always perform all checking, not only those related to the subclass

My best approach so far, as above, was to use protected constructor with flag that avoids calling "incomplete" Base::init() because of virtual functions do not work in superclass constructor. (Without the flag, Base::check() would be called twice.)

My question is: isn't there a better, preferably somehow standard technique, that deals with calling virtual routines after whole object is initialized (pardon for vague terminology)? And of course without requiring users to call init() explicitly (it should stay protected).

One possible use case (mine): Base stands for e.g. an array of general mathematical formulas which must satisfy several constraints. Derived (i.a.) restricts these constraints, add some, can override some particular checks, but mostly still use these from Base. E.g. Base::check_formulas() applies check_formula(f) to every f, and Derived needs to override only check_formula function.

EDIT:

As it is better to avoid virtual functions inside constructors at all, it appears not to be possible to achieve virtual function call from within the object itself, so the object must be constructed "externally" before calling these functions.

Both @StoryTeller and @Caleth suggests deal with this issue, either via dynamic allocation and pointer, or via a function with stack allocation (which is OK with move semantics).

Both of them inspired me to this solution, which is similar to @Caleth's as I found it more simple and straightforward:

template <typename T, typename... Args>
T create(Args&&... args)
{
    T t(forward<Args>(args)...);
    t.init();
    return t;
}

class Base {
public:
    virtual ~Base() = default;
    Base(const Base& rhs) = default;
    Base(Base&& rhs) = default;
    Base& operator=(const Base& rhs) = default;
    Base& operator=(Base&& rhs) = default;

    template <typename T, typename... Args>
    friend T create(Args&&... args);
protected:
    Base() : _arg(0)
    {
        cout << "Base::Base()" << endl;
    }

    Base(int arg) : _arg(arg)
    {
        cout << "Base::Base(int)" << endl;
    }

    virtual void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }
private:
    int _arg;
};

class Derived : public Base {
public:
    virtual ~Derived() = default;

    template <typename T, typename... Args>
    friend T create(Args&&... args);
protected:
    Derived() : Base()
    {
        cout << "Derived::Derived()" << endl;
    }

    Derived(int arg) : Base(arg)
    {
        cout << "Derived::Derived(int)" << endl;
    }

    void init() override
    {
        cout << "Derived::init" << endl;
        Base::init();
        // ...
    }

    void check() override
    {
        cout << "Derived::check" << endl;
        Base::check();
        // ...
    }
};

Usage:

cout << endl << "Base() ..." << endl;
Base b1 = create<Base>();
cout << endl << "Base(int) ..." << endl;
Base b2 = create<Base>(5);
cout << endl << "Derived() ..." << endl;
Derived d1 = create<Derived>();
cout << endl << "Derived(int) ..." << endl;
Derived d2 = create<Derived>(10);

Output:

Base() ...
Base::Base()
Base::init
Base::check

Base(int) ...
Base::Base(int)
Base::init
Base::check

Derived() ...
Base::Base()
Derived::Derived()
Derived::init
Base::init
Derived::check
Base::check

Derived(int) ...
Base::Base(int)
Derived::Derived(int)
Derived::init
Base::init
Derived::check
Base::check

Any other suggestions?

3 Answers3

4

Personally, I just won't let anyone construct those objects directly. If their initialization is brittle, there should be another object holding them, and initializing them in its own constructor. I'd do it via the key-pass idiom.

class BaseHolder;
class Base {
private:
    void init() {
      // do more things
    }
    friend class BaseHolder;
protected:
    class BuildToken {
        explicit BuildToken() {}
        friend class BaseHolder;
    };
    Base(BuildToken) {
      // do your thing.
    }
};

template<typename>
struct MakeType{};

template<typename T>
inline constexpr MakeType<T> make{};

class BaseHolder {
    std::unique_ptr<Base> managed;
public:
    template<typename T>
    BaseHolder(MakeType<T>) : managed(new T(Base::BuildToken())) {
      managed->init();
    }
};

Now no derived class may call init itself, nor may it be called anywhere besides by Base and BaseHolder. All a derived class has to do is define a c'tor that accepts a BuildToken and forwards it to the base. However, derived classes cannot default initialize BuildToken objects themselves, they can only copy them to forward onto their base. The only place the token can be created is in BaseHolder. This is also the only place where init will be called at the proper time.

The utility MakeType is there for making BaseHolder declaration easier on the eyes, to look like this:

BaseHolder item(make<Derived>);
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
2

Don't have any public constructor, but instead have (friend) make_base and make_derived factory functions, which call init on a fully constructed object.

class Base {
public:
    virtual ~Base() = default;
    Base(const Base &) = default;
    Base(Base &&) = default;
    Base& operator=(const Base &) = default;
    Base& operator=(Base &&) = default;

    friend Base make_base() { Base b; b.init(); return b; } 

protected:
    virtual void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }

    Base()
    {
        cout << "Base::Base" << endl;
    }
};


class Derived : public Base {
    friend Derived make_derived() { Derived b; b.init(); return b; }
protected:
    Derived() : Base()
    {
        cout << "Derived::Derived" << endl;
    }

    void init() override
    {
        Base::init();
        cout << "Derived::init" << endl;
        // ...
    }

    void check() override
    {
        Base::check();
        cout << "Derived::check" << endl;
        // ...
    }
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Better idea how to solve that problem with virtual function calls yeah. – πάντα ῥεῖ Nov 29 '18 at 12:37
  • I would like to stick to constructors. I got an "idea" to rename classes to e.g. `Base_` and `Derived_` and the friend functions to `Base` and `Derived`, so that they would act as constructors; but I guess it is bad idea as these identifiers would not represent type names but function names, etc. .. – Tomáš Kolárik Nov 29 '18 at 14:12
  • @TomášKolárik "I would like to stick to constructors" why? This is just as encapsulated, and StoryTeller's is even more so. – Caleth Nov 29 '18 at 14:20
1
  • init() can contain virtual functions inside, but since it should be called only in the final constructor, it should not cause any harm.

Even if it's called through the final constructor any of the vtables for calling these virtual functions aren't initialized at that point.

So you can't guarantee the correct intended behavior.

See more elaborations of the problem here please:

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • I think that the execution is correct in the result, as `Derived` is supposed to be the only and last one from the hierarchy that calls it and virtual mechanism is not necessary at all. But I agree that it is not clean. – Tomáš Kolárik Nov 29 '18 at 14:05