C++ Logo

std-proposals

Advanced search

Re: [std-proposals] c++ proposal for Initializer/finalizer, version 0.1

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Sun, 5 Jul 2026 17:07:42 +0200
niedz., 5 lip 2026 o 16:22 Ruud Rietvink <ruud_at_[hidden]> napisał(a):
>
> On 2026-07-05 15:01, Marcin Jaczewski wrote:
> > niedz., 5 lip 2026 o 14:11 Ruud Rietvink <ruud_at_[hidden]> napisał(a):
> >> 1) I don't suppress base class implementations, like constructors and
> >> destructors they are all called.
> > But I can suppress member initialization if type is trivial:
> > ```
> > struct Baz
> > {
> > int _data[10];
> > Baz() // no zero init of `_data`
> > {
> > }
> > Baz(int i) : _data{ i } // all data are set
> > {
> > }
> > };
> > ```
>
> +class() has nothing to do with variabele initialization.
>

This means you cannot add `+Baz()` there as it would try to work
on unizlied `_data` data?

> >
> >> 2) finalizers are independent of destructors, they are called in a
> >> different line, before the destructor line.
> >> You don't need one to have the other.
> >> 3) Exception handling works the same as with destructors, also
> >> independent of each other.
> > You missed my point, adding this finizers make type not trivial and I
> > do not think its good thing.
> > At least you would need update every place in standard where they
> > refer to not-trivial destructor and add "or finalizer".
> >
> > Similar case we have with `virtual` , what is the point of `virtual
> > -Foo()` when you have normal `~Foo()`?
> >
> > Overall signature of the finalizer should be the same or at least
> > compatible with one from the destructor.
>
> Since finalizing() is separate from destruction(), they may differ in
> virtuality.
>

You repeat this again and I know this, and I think it is wrong, consider:
If `-Foo()` is virtual and `~Foo()` is not there is no case where you can safely
call `-Foo` as virtual function because any call to `~Foo` in that
case will be UB.

> -Foo() is independent of whatever ~Foo() does. And classes that use + or
> - are certainly not trivial.
>

And this means you need to update the definition of triviality.

>
> >> 4) Propogation to member objects? I don't understand what you mean.
> >>
> > ```
> > struct Foo
> > {
> > Bar _member;
> > };
> > ```
> >
> > We have both `-Foo()` and `-Bar()`. When and how `-Bar()` is called?
> > Its called right before `~Foo()` or right before `~Bar()`? or called
> > by `-Foo()`?
> > You can think for case `std::optional<Bar>` too where `Bar` is not a
> > direct member of optional.
> The same order as with destruction:
> First -Foo(), which calls -Bar() at the end, then ~Foo() that calls
> ~Bar() at the end

Then it is impossible to call `-Bar()` when you have `std::optional` outside of
`~Foo()` as it call `~std::optional()` there.
This means:
```
struct Foo1
{
    std::optional<Bar> _v;
};

struct Foo2
{
    std::unique_ptr<Bar> _v;
};

struct Foo3
{
    Bar _v;
};
```

Cannot have the same order of calls or alternatively
calling explicit `_v.~Bar()` does not call `-Bar()`?
And you need for `std::optional` do things like:

```
auto* ptr = new (_storage) Bar();
ptr->+Bar();

//usage

ptr->-Bar();
ptr->~Bar();
```

What version is correct?


> >
> >> On 2026-07-05 13:56, Marcin Jaczewski wrote:
> >>> I had somehow similar idea for "finalizers" but its was more "unwind" operators
> >>> that are called before dstructors is called.
> >>>
> >>> Main difference would be that different types of stack "unwinds" would
> >>> have its own functions
> >>> to make difference between exception unwinding and normal scope exit.
> >>> Or even cancellation of coroutines. Something would be useful for db
> >>> transact scope cancellation.
> >>>
> >>>
> >>> One thing I realized in my solution is that even in your case this new
> >>> `+foo()` and `-foo()` operators
> >>> need to have the same exception specification and triviality as the
> >>> constructor or destructor.
> >>> E.g. adding `-foo() noexcept(false) { throw 1; }` would change the
> >>> result of `noexcept(foo{})`.
> >>> This should be a hard error if the destructor does not have `noexcept(false)`.
> >>> This is needed to avoid unexpectedly changing behavior of other code
> >>> than need to know this things.
> >>>
> >>> Another thing to consider is how you control base functions of this
> >>> initializers and finishers functions?
> >>> How to suppress base class implementation?
> >>> How to propagate it to member objects? And who and when do that?
> >>> Is `virtual ` required on finalizers when you have a `virtual` destructor?
> >>>
> >>> niedz., 5 lip 2026 o 13:20 Ruud Rietvink via Std-Proposals
> >>> <std-proposals_at_[hidden]> napisał(a):
> >>>> C++ object initializer and finalizer
> >>>> ====================================
> >>>> Date: 2026-07-05
> >>>> Version: 0.1
> >>>> Author: Ruud Rietvink
> >>>>
> >>>>
> >>>> Problem
> >>>> -------
> >>>>
> >>>> In the constructor and destructor of an object you cannot use virtual
> >>>> methods properly.
> >>>> So calling a virtual method in a base class to get some possible derived
> >>>> value is not possible.
> >>>> During construction and destruction there is no guaranteed time that the
> >>>> object is fully constructed,
> >>>> so that virtuality covers all the layers of the object.
> >>>>
> >>>>
> >>>> Possible (partial) solutions
> >>>> ----------------------------
> >>>>
> >>>> 1) One solution is to call an initialize() method after construction,
> >>>> and a finalize() method directly before destruction.
> >>>> This is tedious and can easily be forgotten (exceptions, end of scopes).
> >>>>
> >>>> 2) The initialize() could be run automatically in a factory, calling
> >>>> initialize() in the factory create() method.
> >>>> You would have to force that the construction only takes place through
> >>>> this factory by a private/friend construction,
> >>>> or by adding a special factory tag to the constructor arguments.
> >>>> This doesn't solve the finalize() method though, nor copy
> >>>> construction/cloning.
> >>>>
> >>>> 3) With a RAII wrapper, you could call the initialize() method in the
> >>>> RAII constructor and the finalize()
> >>>> in the RAII destructor. Together with a special constructor tag you
> >>>> could force the RAII constructor to be used.
> >>>> This can be made generic. This does require more memory (pointer in RAII
> >>>> object).
> >>>> This fails for copy construction/cloning.
> >>>>
> >>>> 4) This could be done with templated inheritance.
> >>>> template <typename T> class LogWrapper : public T
> >>>> {
> >>>> public:
> >>>> template <typename... args>
> >>>> LogWrapper(T&&... args) : T(std::forward<Args>(args)...)
> >>>> {
> >>>> initializeLog();
> >>>> }
> >>>>
> >>>> ~LogWrapper()
> >>>> {
> >>>> finalizeLog();
> >>>> }
> >>>> }
> >>>>
> >>>> Wrapping could be nested for different purposes
> >>>> (LogWrapper<XXXWrapper<Class>> object(....)).
> >>>> This fails when the class has clone() functionality (unless said API
> >>>> uses some standard copyable in the Wrapper).
> >>>>
> >>>>
> >>>> X) For all wrapper solutions the problem is that when you want to start
> >>>> or stop using it, or extend it, it is a lot of
> >>>> coding effort.
> >>>>
> >>>>
> >>>> Proposal
> >>>> --------
> >>>>
> >>>> Proposal is to introduce an initializer method that is automatically
> >>>> called after the construction is complete,
> >>>> and a finalizer method that is automatically called before the
> >>>> destruction of an object.
> >>>> Because the finalizer is called before the destructing, the vtable is
> >>>> still correct and virtual calling will work correctly.
> >>>> Proposed names:
> >>>> class::+class() for initializing
> >>>> class::-class() for finalizing
> >>>>
> >>>> Initializer is also called after copy construction.
> >>>>
> >>>> Example:
> >>>> class LogBase
> >>>> {
> >>>> protected:
> >>>> virtual ~LogBase() = default;
> >>>> ...
> >>>> virtual const char* const logName() = 0; // or c++26-reflection
> >>>>
> >>>> +LogBase() { logStart(logName()); }
> >>>> virtual -LogBase() { logEnd(logName()); }
> >>>> ...
> >>>> };
> >>>>
> >>>>
> >>>> class EngineBase: protected LogBase
> >>>> {
> >>>> protected:
> >>>> ~EngineBase() override = default;
> >>>> ...
> >>>> virtual EngineType engineType() = 0;
> >>>> +EngineBase() { addToEngine(engineType()); } //
> >>>> Optional, otherwise default
> >>>> -EngineBase() override { removeFromEngine } // Optional
> >>>> ...
> >>>> };
> >>>>
> >>>> class Derived: protected EngineBase
> >>>> {
> >>>> protected:
> >>>> ...
> >>>> EngineType engineType() override { return
> >>>> EngineType.Something; }
> >>>> const char* const logName() override { return "Derived Something"; }
> >>>> ...
> >>>> };
> >>>>
> >>>>
> >>>> EngineBase* base = new Derived();
> >>>> ...
> >>>> delete base;
> >>>>
> >>>>
> >>>> // Order:
> >>>>
> >>>> // LogBase()
> >>>> // EngineBase()
> >>>> // Derived()
> >>>> // Object intact.
> >>>> // +LogBase() (same order as constructor chain)
> >>>> // +EngineBase()
> >>>> // +Derived() (if any)
> >>>> // Life...
> >>>> // -Derived() (if any, same order as destructor chain, for all
> >>>> implementations of -class())
> >>>> // -EngineBase()
> >>>> // -LogBase()
> >>>> // Object still intact.
> >>>> // ~Derived()
> >>>> // ~EngineBase()
> >>>> // ~LogBase()
> >>>>
> >>>>
> >>>> Advantages
> >>>> ----------
> >>>>
> >>>> a) Easy to implement for compilers.
> >>>> b) Easy to optimize away for compilers, if not needed.
> >>>> c) No clashing new keywords.
> >>>> d) Completely optional and backwards compatible.
> >>>> e) If not used, not in the way.
> >>>> f) Could start a new paradigm: construct in (), initialize in +(),
> >>>> finalize in -(), destruct in ~().
> >>>> Also a lot of construction is moved to header file, these days.
> >>>> Also substitutes often used init() method when multiple
> >>>> constructors are present.
> >>>> --
> >>>> Std-Proposals mailing list
> >>>> Std-Proposals_at_[hidden]
> >>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
> >>
>

Received on 2026-07-05 15:07:59