C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Call virtual method from base's destructor

From: Henry Miller <hank_at_[hidden]>
Date: Thu, 30 May 2024 07:57:03 -0500
On option not mentioned yet is put a open variable in your base class and then a contract on your interface - cannot call write() unless open, cannot call the destructor if open. This of course requires contracts which I'm not sure of the current status, hopefully C++26 but.

-- 
  Henry Miller
  hank_at_[hidden]
On Wed, May 29, 2024, at 05:38, Frederick Virchanza Gotham via Std-Proposals wrote:
> I wrote a class to manage a COM port on a microcontroller (i.e. RS232
> traffic). The destructor didn't need to do anything so I started out
> with:
>
>     class IRS232 {
>     public:
>         virtual ~RS232(void) noexcept {}
>     };
>
>     class RS232 : public IRS232 {};
>
> Later I had to write an emulator/simulator to run the firmware on a
> desktop PC, and so now I had to close the COM port upon destruction. I
> wanted to put the 'Close' operation inside the base class rather than
> have to put it in all the derived classes (e.g. RS232_Microcontroller,
> RS232_Win32, RS232_Boost, etc). So I started out with:
>
>     class IRS232 {
>     public:
>         virtual void Close(void) noexcept = 0;
>         virtual ~RS232(void) noexcept { this->Close(); }
>     };
>
> Of course, the problem here is that if we make another class such as
> "RS232_Win32" and derive it from IRS232, the RS232_Win32 part of the
> object has already been destroyed by the time the base class's
> destructor is called, and so the invocation of the 'Close' method
> might try to access an object that is either in an invalid state or no
> longer exists.
>
> Of course what needs to be done here, is that the derived classes
> (e.g. "RS232_Win32" and "RS232_Boost"), must invoke the 'Close' method
> inside their own destructors. To spell it out:
>
>     RS232_Microcontroller::~RS232_Microcontroller   must call    "this->Close()"
>     RS232_Win32::~RS232_Win32   must call    "this->Close()"
>     RS232_Boost::~RS232_Boost   must call    "this->Close()"
>
> Still though  . . . I feel like the base class should have something
> written between its two curly braces to do either one of two things:
>     (1) Inform the programmer that their derived class should close
> the COM port upon destruction
>     (2) Compel the programmer to make their derived class close the
> COM port upon destruction
>
> No. 1 can be achieved with a simple comment as follows:
>
>     class IRS232 {
>     public:
>         virtual void Close(void) noexcept = 0;
>         virtual ~RS232(void) noexcept
>         {
>             // Make sure all derived classes
>             // close the COM port in their
>             // own destructors, i.e.
>             //        this->Close();
>         }
>     };
>
> I was thinking that No.2 could be achieved something like:
>
>     class IRS232 {
>     public:
>         virtual void Close(void) noexcept = 0;
>         virtual ~IRS232(void) noexcept derived_invokes(Close) {}
>     };
>
> Focusing on this one line:
>
>         virtual ~IRS232(void) noexcept derived_invokes(Close) {}
>
> Its effect is as follows:
>
>     "Any class derived from IRS232 must contain in its destructor, at
> least one invocation of the 'Close' method (even if it's inside the
> body of an 'if' statement)."
>
> Similarly if you wanted to make sure that the "Eat_Dinner" method
> invokes the "Wash_The_Dishes_Afterward" method, you would do:
>
>         virtual void Eat_Dinner(void) derived_invokes(Wash_The_Dishes);
>
> So then when the compiler goes to compile the translation unit which
> contains the definitions of Lazy_Person's methods, it encounters:
>
>     void Lazy_Person::Eat_Dinner(void)
>     {
>         return;     // doesn't call "Wash_The_Dishes"
>     }
>
> You get the following compiler error:
>
> main.cpp:11:18: error: method 'Eat_Dinner' in derived class ‘Lazy_Person’ does
>                                     not invoke ‘Wash_The_Dishes’, but
> method in base
>                                     class ‘Person’ is marked
> ‘derived_invokes(Wash_The_Dishes)’
>
> Of course though this would beg the question . . . what if there's two
> kinds of Boost port, such as:
>
>     class RS232_Boost_Win32 : public RS232_Boost { . . . };
>     class RS232_Boost_FreeBSD : public RS232_Boost { . . . };
>
> So now we don't want the destructor of "RS232_Boost" to invoke
> "Close", but we do want the destructor of "RS232_Boost_Win32" to
> invoke "Close", so how do we say this to the compiler? Well how about
> the following:
>
>     RS232_Boost::~RS232_Boost(void)
>     {
>         derived_invokes(Close);
>         // Tells the compiler to pass the obligation
>         // down to the next derived class
>     }
>
>     RS232_Boost_Win32::~RS232_Boost_Win32(void)
>     {
>         this->Close();
>     }
>
> And you can pass the obligation down as far as you want, for example:
>
>     RS232_Boost::~RS232_Boost(void)
>     {
>         derived_invokes(Close);
>     }
>
>     RS232_Boost_Win32::~RS232_Boost_Win32(void)
>     {
>         derived_invokes(Close);
>     }
>
>     RS232_Boost_Win32_x86::~RS232_Boost_Win32_x86(void)
>     {
>         derived_invokes(Close);
>     }
>
>     RS232_Boost_Win32_x86_64::~RS232_Boost_Win32_x86_64(void)
>     {
>         this->Close();
>     }
>
> The purpose of this proposed new feature is that instead of creating
> bugs that are discovered a runtime, you get a compiler error.
> -- 
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2024-05-30 12:57:29