Home > Enterprise >  Default parameters and forward declaration
Default parameters and forward declaration

Time:10-07

I have a class Property with a constructor in which I want default parameters, in a file property.h:

class Property {
  Property(OtherClass* value = myApp->someValue) {...};
};

where myApp, of another type Application, is defined in another file that makes extensive use of this Property class. Since this other file #includes property.h, of course, I cannot #include this file in property.h

property.h does not know about myApp nor Application (though property.cpp does, and OtherClass is known in property.h). I could forward declare OtherClass and declare myApp as extern, but this results in an error C2027 "use of undefined type Application", as expected :

class Application;
extern Application* myApp;
class Property {
  Property(OtherClass* value = myApp->someValue) {...};
};

A solution could be to have the default parameter in the .cpp file, but this is not advisable (here).

How can I get this default parameter working ? (i.e., not another solution that would involve not having default parameters telling me default parameters are evil, or that my design is poor to start with etc. ;) ). Thanks!

CodePudding user response:

First of all, to use a member of the Application class you must have the full definition of the Application class. Otherwise the compiler will not know about the members of the class.

Secondly, you should probably make the Property constructor explicit to inhibit implicit conversions from Application. Such implicit conversion tend to make code harder to read, understand and maintain.

Thirdly, and as a possible way to work around the first problem, you could use overloading to have two Property constructors: One with a non-default Property* argument, and one without any argument at all. The argument-less constructor delegates to the one-argument constructor for initialization. This split into two constructor function will make it easier to define (implement) separate from declaration.


In code my solution would look something like this:

First the Property header file

// Forward declaration only, no header file needed
class OtherClass;

class Property
{
public:
    Property();
    explicit Property(OtherClass* other);

    // ...
};

Then the Property class source file

#include "Property.h"

// Get the full and complete definition of the Application class
#include "Application.h"

extern Application* myApp;

// Will probably need the full definition of the OtherClass as well
#include "OtherClass.h"

// Default constructor delegates to one-argument constructor
Property::Property()
    : Property{ myApp->GetOtherObject() }
{
}

Property::Property(OtherClass* other)
{
    // Use the other class here...
}

CodePudding user response:

The remarkable point is that the default argument of function parameters is resolved where the function is called (in opposition to where the function is defined). That gives the necessary space to solve OPs problem by yet another level of indirection:

Instead of accessing myApp->someValue, a helper function is introduced, which can be declared before the definition of Property but implemented after Application is fully defined. (This could be a static member function of Property as well.)

An example to demonstrate:

#include <iostream>

int getAppDefault();

struct Application;
extern Application *pApp;

struct Property {
  int value;
  Property(int value = getAppDefault()): value(value) { }
};

struct Application {
  int valueDefault = 123;
};

Application app;
Application *pApp = &app;

int getAppDefault() { return pApp->valueDefault; }

int main()
{
  Property prop1;
  std::cout << "prop1: " << prop1.value << '\n';
  app.valueDefault = 234;
  Property prop2;
  std::cout << "prop2: " << prop2.value << '\n';
}

Output:

prop1: 123
prop2: 234

Demo on coliru

To emphasize that value = getAppDefault() is in fact resolved when the constructor Property::Property() is called, I modified the Application::valueDefault before default-constructing prop2.


And, of course, this works as well if the Property stores a pointer instead of the value itself:

Demo on coliru

  • Related