>
>
>
V1101. Changing the default argument of…


V1101. Changing the default argument of a virtual function parameter in a derived class may result in unexpected behavior.

The analyzer has detected a virtual function that has a parameter with a default argument. Default arguments are defined in the base and derived classes; their values are different. Changing the default argument of a virtual function parameter in this way is not an error, but it can lead to unexpected results when using these classes.

Take a look at the example:

struct Base
{
  virtual void foo(int i = 0) const noexcept
  {
    std::cout << "Base::foo() called, i = " << i << std::endl;
  }
};

struct Derived : Base
{
  void foo(int i = 10) const noexcept override
  {
    std::cout << "Derived::foo() called, i = " << i << std::endl;
  }
};

In the 'Base' class, the 'foo' virtual function is defined with one 'i' parameter that has a default argument of '0'. In the 'Derived' class, which is derived from 'Base', the 'foo' virtual function is overridden and the default argument of the 'i' parameter is changed to '10'.

Let's see what issues such overriding may cause. Let's say we use the code as follows:

int main()
{
  Derived obj;
  Base *ptr = &obj;
  ptr->foo();
}

The 'main' function will return an unexpected string — "Derived::foo() called, i = 0". When forming the 'foo' function call, a compiler takes the static type of an object under the 'ptr' pointer — 'Base'. Therefore, the default argument of '0' from the base class is substituted in the function call. At the same time, the 'ptr' variable actually points to an object of the 'Derived' type. So, the virtual function from the derived class is executed.

To avoid this kind of behavior, we recommend using one of the following strategies:

  • don't use default arguments in virtual functions;
  • define the default argument of the virtual function parameter only in the base class.

Here's the correct example:

struct Base
{
  virtual void foo(int i = 0) const noexcept
  {
    std::cout << "Base::foo() called, i = " << i << std::endl;
  }
};

struct Derived : Base
{
  void foo(int i) const noexcept override
  {
    std::cout << "Derived::foo() called, i = " << i << std::endl;
  }
};

Note. The analyzer does not issue any warnings for the following code:

struct Base
{
  virtual void foo(int i = 0) const noexcept
  {
    std::cout << "Base::foo() called, i = " << i << std::endl;
  }
};

struct Derived : Base
{
  void foo(int i = 0) const noexcept override
  {
    std::cout << "Derived::foo() called, i = " << i << std::endl;
  }
};

However, we do not recommend writing such code because it is more difficult to maintain.

This diagnostic is classified as: