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