Michel H
Michel H

Reputation: 363

Enforce initialization behaviour in abstract class constructor for all future derived classes

I'm deriving from an existing input field class from the Qt Library (not necessary to be familiar with it), where I'm adding a text validator that checks for valid inputs (using the QLineEdit validation options) and some further functionalities/overrides.

For this I create a pure virtual class called ConstrainedQLineEdit which derives from QLineEdit, from which then I implement specific input fields (e.g. PasswordQLineEdit). Here a simplification of my implementation:

Abstract Base class:

class ConstrainedQLineEdit : public QLineEdit
{
Q_OBJECT // QT macro that implements observer pattern with signals/slots among others (not relevant)
public:
  virtual ~ConstrainedQLineEdit()
  {
      // delete validator;
  }

protected:
  ConstrainedQLineEdit(QWidget* parent = nullptr)
      : QLineEdit(parent) // , validator(nullptr)
  {
      _initialize();
  }

  virtual void _initialize()
  {
      QString validatorInformation = _getValidatorInformation();
      // validator = new Validator{validatorInformation};
      // further logic 
  }

  virtual QString _getValidatorInformation() const = 0;
  // Validator validator;
  // Further variables and necessary overrides of QLineEdit
};

Derived class for passwords

class PasswordQLineEdit : public ConstrainedQLineEdit
{
  Q_OBJECT // idem
public:
  PasswordQLineEdit(QWidget* parent = nullptr)
      : ConstrainedQLineEdit(parent)
  {}

  virtual ~PasswordQLineEdit() = default;

protected:
  QString _getValidatorInformation() const override
  {
      QString validatorInformation;
      // logic generating the information
      return validatorInformation;
  }
};

Executable

int main(int argc, char** argv)
{
    QApplication application(argc, argv);
    PasswordQLineEdit passwordQLineEdit;
    passwordQLineEdit.show();
    return application.exec();
}

Upon executing I get the following error (without debugger the application crashes segmentation-fault style):

pure virtual method called
terminate called without an active exception

My question:

I'm aware that it is frowned upon to call a virtual function from the base class constructor since it can lead to exactly this kind of problems. I'm also aware I could just put the initialization in the derived-class constructor.

However, this is a base class that my team will use in the future to create further custom fields, and I want to specifically enforce (or pre-define) the behaviour of how derived classes are constructed so that other team-members really only have to define a constructor that passes the same arguments and then the validation logic via QString _getValidationInformation() override and be done with it.

Any design recommendations to achieve this without breaking the class structure (I'm overriding further virtual functions of QLineEdit and there's additional logic)?

Upvotes: 1

Views: 190

Answers (1)

BobMorane
BobMorane

Reputation: 4296

I'm aware that it is frowned upon to call a virtual function from the base class constructor

It is not only "frowned upon", it is wrong. Writing such code will lead to bugs. Your error comes from the fact that your child class virtual method is never called, in the construction process, only the base class methods are called. Hence, it tries to call the _getValidatorInformation() from the base class, but it has not been implemented, hence the error. If you had provided a default implementation, then the compiler would have said nothing and you would have ad bugged code.

What I would suggest is to use a factory function to create your object. The factory function would be responsible to

  1. Create the object.
  2. Validate the preconditions.

Some pseudo code to show the idea:

std::unique_ptr<ConstrainedQLineEdit> Create(EType type_)
{
    std::unique_ptr<ConstrainedQLineEdit> lineEdit;

    switch(type_)
    {
        case EType::Password:
            lineEdit = std::make_unique<PasswordQLineEdit>();
      
        // You can extend and add types here...
    }

    // At this point, construction is completed. You can use virtual methods!
    lineEdit->Initialize();

    return lineEdit;
}

Upvotes: 2

Related Questions