问题
Wt recommends to use forward declarations to avoid circular dependencies.
// Settings.h
#include <Wt/Dbo/Dbo.h>
#include <string>
class User; // Forward declaration of User Wt::Dbo object
class Settings
{
public:
Wt::Dbo::ptr<User> user;
template<class Action>
void persist(Action& a)
{
Wt::Dbo::belongsTo(a, user);
}
};
// User.h
#include <Wt/Dbo/Dbo.h>
#include <string>
#include "Settings.h"
class User
{
public:
Wt::Dbo::weak_ptr<Settings> settings;
template<class Action>
void persist(Action& a)
{
Wt::Dbo::hasOne(a, settings);
}
};
However, when I use this Settings
class in another cpp file, the program doesn't compile:
// test.cpp
#include "Settings.h"
error: C2079: 'dummy' uses undefined class 'User'
Possible solutions (which I do not like)
A solution is to include in
User.h
in every cpp file that includesSettings.h
, i.e.:// test.cpp #include "User.h" #include "Settings.h"
I do not prefer this solution, because I have to remember to include
User.h
every time I includeSettings.h
.Another solution is to use the non-recommended
DBO_EXTERN_TEMPLATES
macro, i.e.// Settings.h ... class Settings { public: .... }; DBO_EXTERN_TEMPLATES(Settings)
I do not prefer this solution as this macro is not recommend, nor documented.
DBO_EXTERN_TEMPLATES
doesn't work with all compilers.
Question
a. What is the best/preferred methodology to overcome circular dependencies between Wt::Dbo
objects avoiding the mentioned undefined class
error?
b. Why does solution 1. works?
I created a new (general - not Wt::Dbo
specific) question (with an MCVE), to clarify the specific situation: When are member functions of a templated class instantiated?
References
- DBO_EXTERN_TEMPLATES: https://www.mail-archive.com/witty-interest@lists.sourceforge.net/msg06963.html
- Wt::Dbo and circular depencies: https://redmine.webtoolkit.eu/boards/2/topics/290?r=292
- The given example is based on the
Wt::Dbo
tutorial: https://www.webtoolkit.eu/wt/doc/tutorial/dbo.html#_em_one_to_one_em_relations, but I want to place the different classes into different header files.
回答1:
I'm not familiar with Wt::Dbo
, but I don't believe the issue is specific with it. It is more a general C++ class design issue, which you need to work around/with; it is actually rather common in C++ projects.
For the "best/preferred method," that really is a matter of opinion. In your case, you can actually have both User.h
and Settings.h
include each other, if you still have the forward declarations.
For example, in Settings.h
:
// include guard
class User;
#include "User.h"
class Settings { ... };
Then in User.h
, you can do:
// include guard
class Settings;
#include "Settings.h"
class User { ... };
I know this seems odd, but it is a way to ensure you don't have to include both headers all the time. Alternatively, you just do that in one header, and ensure that is the one you always include.
In general, my preferred way is, in header files, only include what is absolute needed in the header, and forward declare the rest. In the source files, I then include the headers which are actually needed. The reason for this is because then, if I need to change one header file, I don't have to recompile all the source files which included that header; it improves the performance of the compilation process.
As for your question as to why solution 1 works, it's because of how you are including the files. In that specific example, you don't even need to include Settings.h
in the source file, because User.h
already does this. But let's look at how it looks once the pre-processor is done with it.
When you include User.h
, it first includes Settings.h
. An include basically copies the contents into the current file where the include occurred. So, effectively, your User.h
would look something like this:
// User.h
#include <Wt/Dbo/Dbo.h> // contents from this would be here
#include <string> // contents from this would be here
// Settings.h
#include <Wt/Dbo/Dbo.h> // contents NOT included, due to previous include and include guards
#include <string> // same as above
class User; // Forward declaration of User Wt::Dbo object
class Settings
{
public:
Wt::Dbo::ptr<User> user;
template<class Action>
void persist(Action& a)
{
Wt::Dbo::belongsTo(a, user);
}
};
class User
{
public:
Wt::Dbo::weak_ptr<Settings> settings;
template<class Action>
void persist(Action& a)
{
Wt::Dbo::hasOne(a, settings);
}
};
What you can see now, is that when the Settings
class is being defined, the User
is already forward declared and can be used by the Settings
class. When User
is then defined, it has the full definition of Settings
to work with. In your test.cpp
file now, both Settings
and User
are fully defined, and hence can be used.
I hope this helps :)
回答2:
Based on the answer of ChrisMM, another solution is to forward declare your class at the top of its header file:
Settings.h:
// include guard
class Settings;
#include "User.h"
class Settings { ... };
Users.h:
// include guard
class User;
#include "Settings.h"
class User { ... };
The advantage of this approach is that you only have to forward declare the class in its own header file and are allowed to just include the file in any other (header) file that need it.
来源:https://stackoverflow.com/questions/59428743/circular-dependency-in-wtdbo