【问题标题】:Circular dependency with template function带模板函数的循环依赖
【发布时间】:2017-10-16 21:32:19
【问题描述】:

我有一个带有以下声明的 A 类(A.h 文件):

#ifndef __A_DEFINED__
#define __A_DEFINED__

class A
{
public:
  template<typename T> inline void doThat() const;
};

#endif

以及从该类派生的 B 类(B.h 文件):

#ifndef __B_DEFINED__
#define __B_DEFINED__

#include <iostream>
#include "A.h"

class B : public A
{
public:
  void doThis() const { std::cout << "do this!" << std::endl; }
};

#endif

到目前为止,一切都很好。我的问题是函数 A::doThat() 使用 B::doThis():

template<typename T> inline void A::doThat() const { B b; b.doThis(); }

通常,循环依赖不会成为问题,因为我只需在 .cpp 文件中定义 A::doThat()。然而,就我而言,doThat 是一个模板函数,所以我不能这样做。

以下是我目前设想的解决方案:

  1. 在 .cpp 文件中定义模板函数 A::doThat()。问题是我需要使用各种模板参数显式实例化所有调用(实际情况下可能有很多)。

  2. 在A.h中声明A类后,添加#include "B.h",然后定义A::doThat()函数。这在 Visual Studio 中运行良好,但 g++ 不喜欢它。

有没有巧妙的方法来解决这个问题?

编辑: 在实际情况下,不只有一个子类 B,而是多个(B、C、D 等)函数 A::doThat() 依赖于所有他们。函数 B::doThis() 也是模板化的。

【问题讨论】:

标签: c++ templates dependencies circular-dependency


【解决方案1】:

B 类的默认模板参数可以工作:

#include <iostream>

// include A.h
class B;

class A
{
public:
    template<typename T, typename U = B> inline void doThat() const 
    {
        U b; b.doThis();
    }
};

// include B.h
class B : public A
{
public:
    void doThis() const { std::cout << "do this!" << std::endl; }
};

// main
int main()
{
    A a;
    a.doThat<int>();
}

【讨论】:

  • 这仅适用于默认模板参数,或者即使在此处定义函数?我这样说是因为在实际情况下,doThat() 依赖于 B,但也依赖于子 C、D 和 E。
  • @Touloudou 这听起来像是一个相关但不同的问题,但答案不同。
【解决方案2】:

通常允许父函数调用子函数的最好方法是在父函数中将该函数声明为纯虚函数,并在子函数中覆盖它。

#include <iostream>

class A
{
public:
    virtual ~A() = default;
    template<typename T> inline void doThat() const
    {
        // do some other stuff
        doThis();
    }
    virtual void doThis() const = 0; // pure virtual function
};

class B: public A
{
public:
    void doThis() const override
    {
        std::cout << "do this!" << std::endl;
    }
};

int main()
{
    B b;
    A* ap = &b;
    ap->doThat<int>();
}

【讨论】:

  • 这里的问题是 doThis() 函数也应该在真实案例中进行模板化。这意味着这将是一个虚拟模板函数,这在当前的 c++ 中是不行的。
  • @Touloudou 这是问题中非常重要的信息。您应该编辑问题并添加它。如果它是在没有(或使用简单)参数的情况下进行模板化的,您可以将模板化函数抽象为常规函数。
【解决方案3】:

以下内容适用于g++

文件A.h

#ifndef __A_DEFINED__
#define __A_DEFINED__

class A
{
public:
  template<typename T> inline void doThat() const;
};

#include "B.h"

template<typename T> inline void A::doThat() const { B b; b.doThis(); }

#endif

文件B.h

#include <iostream>

#include "A.h"

// We check for the include guard and set it AFTER the inclusion of A.h
// to make sure that B.h is completely included from A.h again.
// Otherwise the definition of A::doThat() would cause a compiler error
// when a program includes B.h without having included A.h before.
#ifndef __B_DEFINED__
#define __B_DEFINED__

class B : public A
{
public:
  void doThis() const { std::cout << "do this!" << std::endl; }
};

#endif

文件test_A.cpp

// In this test case we directly include and use only A.
#include "A.h"
#include "A.h" // We test whether multiple inclusion causes trouble.

int main() {
    A a;
    a.doThat<int>();
}

文件test_B.cpp

// In this test case we directly include and use only B.
#include "B.h"
#include "B.h" // We test whether multiple inclusion causes trouble.

int main() {
    B b;
    b.doThat<int>();
    b.doThis();
}

另类想法:

我不知道您(或某些编码约定)是否坚持为每个类使用单独的头文件,但如果不是,以下应该可以工作:

您可以将class Aclass B 的定义以及成员函数模板A::doThat&lt;typename&gt;()(按此顺序)放在一个头文件AandB.h(或任何您喜欢的名称)中。

【讨论】:

  • @Touloudou 根据您的想法 2,我在使用 g++ 时遇到了问题。我不得不移动包含防护(参见 B.h 中的评论)。
【解决方案4】:

这需要多态性。使用多态有两种选择:

  1. 动态多态性,即将A 设为抽象基类并虚拟调用doThis()

    struct A
    {
        virtual void do_this() const = 0;
        template<typename T>
        void doThat() const { doThis(); }
    };
    
    struct B : A
    {
        void doThis() const override { /* ... */ }
    };
    

    当然,这仅适用于 doThis() 未模板化的情况。如果需要,可以使用

  2. 静态多态性,即CRTP,当

    template<typename Derived>
    struct A
    {
        template<typename T>
        void doThat() const { static_cast<const Derived*>(this)->template doThis<T>(); }
    };
    
    struct B : A<B>
    {
        template<typename T>
        void doThis() const { /* ... */ }
    };
    

如果(在您的示例代码中)B::doThis() 不是为同一个对象调用的,但对于某些临时对象,您可以

template<typename typeB>
struct A
{
    template<typename T>
    void doThat() const { typeB b; b.template doThis<T>(); }
};

【讨论】:

    猜你喜欢
    • 2015-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-07
    • 2012-03-09
    相关资源
    最近更新 更多