Wt::Dbo 中的循环依赖

Circular dependency in Wt::Dbo

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 class 时,程序无法编译:

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

error: C2079: 'dummy' uses undefined class 'User'

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

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

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

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

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

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

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

问题

一个。什么是 best/preferred 方法来克服 Wt::Dbo 对象之间的循环依赖,避免提到的 undefined class 错误?

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

我创建了一个新的(一般 - 不是 Wt::Dbo 特定的)问题(带有 MCVE),以阐明具体情况:

参考资料

我不熟悉 Wt::Dbo,但我不认为这个问题与它有关。它更像是一个通用的 C++ class 设计问题,您需要解决这个问题 around/with;它实际上在 C++ 项目中很常见。

对于"best/preferred method,"那还真是见仁见智了。在你的情况下,如果你仍然有前向声明,你实际上可以让 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 { ... };

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

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

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

当您包含 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 class 被定义时, User 已经被前向声明并且可以被 Settings 使用class。然后定义 User 时,它具有要使用的 Settings 的完整定义。现在在您的 test.cpp 文件中,SettingsUser 都已完全定义,因此可以使用。

希望对您有所帮助:)

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

Settings.h:

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

class Settings { ... };

Users.h:

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

class User { ... };

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