Another gotcha in programming

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Another gotcha in programming

Post by albinopapa » June 2nd, 2020, 6:28 am

Man, learn new stuff every freaking day, and the lessons learned aren't always what you expect or want.

So, here's the setup:

Code: Select all

class basic_window{
public:
     basic_window( point pt_, window_style style_, std::wstring title_)
     {
          // Does this call the derived class's override version?
          _on_create( pt, style_, std::move( title_ ) );
     }
protected:
     virtual void _on_create( point, window_style, std::wstring ) = 0;
};

class default_window : public basic_window {
public:

private:
     void _on_create( point, window_style, std::wstring) override;
};
So look over the code and see what you think should happen when you try creating a default_window.

If you don't see anything wrong with this call to _on_create() you'd be in the same boat as me. Unfortunately, this is not the case. The compiler seems to expect basic_window::_on_create to have a definition even though it's pure virtual. After looking online, it turns out this is expected behavior as default_window hasn't been fully instantiated yet, calling _on_create() in this case want to call the basic_window version.

So now I'm in a bit of a design pickle, AGAIN!!! I'm trying to keep with RAII idoms here dammit, so I don't want to two step initialization by having a separate void basic_window::create() function and it hardly makes sense to expect derived classes to call the override function upon creation does it?

The options I see before me are:
  • Force derived classes to call _on_create() during their construction instead of the base class
  • Move definition to basic_window::_on_create() and remove default_window::_on_create()
  • Two stage initialization where basic_window::basic_window() is just a default constructor, and users must call basic_window::create() which then CAN call default_window::_on_create() because default_window will have been technically "fully initialized".
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: Another gotcha in programming

Post by Slidy » June 2nd, 2020, 4:20 pm

I got this one a few days ago as well. It bites me in the ass pretty often, I never learn my lesson. Worst part is that you get a garbage runtime error that doesn't explain anything.

My usual approach is your 3rd option (maybe done using factory function if applicable).

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Another gotcha in programming

Post by albinopapa » June 2nd, 2020, 6:48 pm

I don't particularly like two stage initialization though, that's the thing. If I do have a default constructor, it's only to appease std::array or C-arrays or if it makes sense to allow such a thing, otherwise I'd rather just have a constructor that initializes all relevant members. In this example, it doesn't make sense to have a uninitialized window.

The biggest reason not to have uninitialized objects is the need to check it's state before using most functionality of the object.

If I go with option 1, I continue to have the flexibility of keeping basic_window pretty generic and have all modifications handled through derived classes.
If I go with option 2, I restrict a basic_window to a single implementation.
If I go with option 3, I have now introduced a window with a possible invalid state that constantly have to be checked for validity.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Another gotcha in programming

Post by albinopapa » June 2nd, 2020, 7:10 pm

Oh, I didn't get a runtime error message, it was a linker error message saying that there was an undefined symbol, but if you are getting a runtime error, then it might be because your virtual function wasn't a pure virtual function: virtual void func()=0;

I very rarely define virtual function, mine are mostly pure virtual functions. If a derived class doesn't implement that behavior, override it was an empty function for the most part. I suppose the hardest situation would be actually having a default implementation that needs to be overridden in some derived classes, but not others aside from just reusing the same code in the derived classes as you would the base class's default implementation.

The idiom the STL uses of base class has a public interface of non-virtual methods and a protected interface for virtual methods is kind of nice. It allows common tasks to be done either before or after a virtual function call. As an example, std::pmr::memory_resource uses this idiom. For Microsoft's implementation at least, the alignment of bytes requested is verified to be a power of two before passing the values of size and alignment to the implementation of the virtual function do_allocate().
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Another gotcha in programming

Post by chili » July 5th, 2020, 3:17 pm

Yeah, this is something I've often wanted, but never really found a truly satisfying solution to. Outlined in some detail here as well:

https://isocpp.org/wiki/faq/strange-inh ... from-ctors
https://isocpp.org/wiki/faq/strange-inh ... ctor-idiom
Chili

Post Reply