Circular dependency in Wt::Dbo

 ̄綄美尐妖づ 提交于 2020-01-13 11:09:11

问题


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)

  1. A solution is to include in User.h in every cpp file that includes Settings.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 include Settings.h.

  2. 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!