Wt::D bo 中的循环依赖关系

Circular dependency in Wt::Dbo

本文关键字:循环 依赖 关系 bo Wt      更新时间:2023-10-16

Wt 建议使用前向声明以避免循环依赖。

// 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);
}
};

但是,当我在另一个 cpp 文件中使用此Settings类时,程序无法编译:

// test.cpp
#include "Settings.h"

错误: C2079:"虚拟"使用未定义的类"用户">

可能的解决方案(我不喜欢(

  1. 一种解决方案是包含在包含Settings.h的每个 cpp 文件中的User.h中,即:

    // test.cpp
    #include "User.h"
    #include "Settings.h"
    

    我不喜欢这个解决方案,因为我必须记住每次包含User.h时都包含Settings.h.

  2. 另一种解决方案是使用不推荐的DBO_EXTERN_TEMPLATES宏,即

    // Settings.h
    ...
    class Settings
    {
    public:
    ....
    };
    DBO_EXTERN_TEMPLATES(Settings)
    

    我不喜欢这个解决方案,因为不推荐这个宏,也不记录。DBO_EXTERN_TEMPLATES不适用于所有编译器。

问题

一个。克服Wt::Dbo对象之间的循环依赖关系的最佳/首选方法是什么,避免上述undefined class错误?

b.为什么解决方案 1. 有效?

我创建了一个新的(一般 - 不是Wt::Dbo特定(问题(使用 MCVE(,以澄清具体情况:模板化类的成员函数何时实例化?

引用

  • DBO_EXTERN_TEMPLATES:https://www.mail-archive.com/witty-interest@lists.sourceforge.net/msg06963.html
  • Wt::D bo和循环:https://redmine.webtoolkit.eu/boards/2/topics/290?r=292
  • 给定的示例基于Wt::Dbo教程:https://www.webtoolkit.eu/wt/doc/tutorial/dbo.html#_em_one_to_one_em_relations,但我想将不同的类放入不同的头文件中。

我不熟悉Wt::Dbo,但我认为这个问题并不具体。这更像是一个一般的C++类设计问题,您需要解决/处理;这实际上在C++项目中相当常见。

对于"最佳/首选方法",这确实是一个意见问题。在您的情况下,如果您仍然具有前向声明,您实际上可以同时包含User.hSettings.h

例如,在Settings.h

// include guard
class User;
#include "User.h"
class Settings { ... };

那么在User.h,你可以做到:

// include guard
class Settings;
#include "Settings.h"
class User { ... };

我知道这看起来很奇怪,但这是一种确保您不必一直包含两个标题的方法。或者,您只需在一个标头中执行此操作,并确保始终包含该标头。

一般来说,我的首选方法是,在头文件中,只包含头文件中绝对需要的内容,并转发声明其余部分。在源文件中,我包括实际需要的标头。这样做的原因是,如果我需要更改一个头文件,我不必重新编译包含该头文件的所有源文件;它提高了编译过程的性能。

至于您关于为什么解决方案1有效的问题,那是因为您如何包含文件。在该特定示例中,您甚至不需要在源文件中包含Settings.h,因为User.h已经这样做了。但是,让我们看看预处理器完成后它的外观。

当您包含User.h时,它首先包括Settings.h。包含基本上将内容复制到发生包含的当前文件中。因此,实际上,您的User.h如下所示:

// 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);
}
};

您现在可以看到的是,在定义Settings类时,User已经向前声明并且可以由Settings类使用。然后定义User时,它具有要使用的Settings的完整定义。现在在您的test.cpp文件中,SettingsUser都是完全定义的,因此可以使用。

我希望这对:)有所帮助

根据 ChrisMM 的回答,另一种解决方案是在其头文件的顶部转发声明您的类:

设置.h:

// include guard
class Settings;
#include "User.h"
class Settings { ... };

用户.h:

// include guard
class User;
#include "Settings.h"
class User { ... };

此方法的优点是,您只需在其自己的头文件中转发声明类,并且只允许将该文件包含在需要它的任何其他(头(文件中。