Date: Sun, 5 Jul 2026 15:01:03 +0200
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
{
}
};
```
> 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.
> 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.
> 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
>
>
>
> 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
{
}
};
```
> 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.
> 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.
> 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 13:01:19
