如何使用“using”指令拆分循环依赖关系

how to split cyclic dependency with 'using' directive

提问人:Eyal Kamitchi 提问时间:7/19/2023 更新时间:7/19/2023 访问量:70

问:

假设类 A 和 B。每个都可以构造另一个:

// A.hpp
#pragma once
class B;
class A {
  B foo();
};
// A.cpp
#include <A.hpp>
#include <B.hpp>
B foo(){ return B(); }
// B.hpp
#pragma once
class A;
class B {
  A foo();
};
// B.cpp
#include <B.hpp>
#include <A.hpp>
A foo(){ return A(); }

让我们介绍一种新类型:

// A_or_B.hpp
#pragma once

#include "A.hpp"
#include "B.hpp"
// or
class A;
class B;

using A_or_B = std::variant<A, B>;

让我们从 A 和 B 返回A_or_B:

// A.hpp
#pragma once

#include "A_or_B.hpp" // woops!  the type for A_or_B is not resolved:
class A {
  A_or_B foo();
};

哎呀呀的理由:

  1. 如果我们在 A_or_B.hpp 中使用 include,那么A_or_B将无法包含 A(因为标头保护) - 因此它将引用不存在的类型。
  2. 如果我们使用正向声明,那么A_or_B将使用不完整的类型,并且在链接阶段将不可用

和 之间不会出现循环问题,因为我们在 和 文件之间拆分了声明和实现。与类相反 - 或指令不能拆分。ABhppcpptypedefsusing

可以做些什么来解决这个问题?

使用 循环依赖关系 C++ 标头 TypeDef

评论

0赞 Yksisarvinen 7/19/2023
你要解决的根本问题是什么?为什么两者都应该有一个返回 or 或 的方法?ABAB
0赞 Eyal Kamitchi 7/19/2023
它是一个状态机。类型的对象是当前状态。类本身就是状态。A_or_B
1赞 Nelfeal 7/19/2023
做一个实际的类。A_or_B
2赞 user7860670 7/19/2023
与正向声明的类一起使用是 UB。这里的循环依赖关系无法解决,因为每个步骤都需要完整的类型。它可以通过在某处引入指针来轻松解决,例如,甚至可能是类的层次结构std::variant<A, B>;using A_or_B = std::variant<A *, B *>;using A_or_B = ::std::unique_pt<Base>;
0赞 Yksisarvinen 7/19/2023
那么,为什么不采用典型的继承方式呢?如上所述,你不能做你想做的事。class State {virtual State* foo() = 0;}; class A: public State{/*override foo*/}; class B: public State{/*override foo*/}};

答:

2赞 jwezorek 7/19/2023 #1

您可以转发声明,然后最终将其定义为公开继承自 variant 类型的类。A_or_B

// A.hpp
#pragma once
class A_or_B;
class A {
    A_or_B foo();
};

// B.hpp
#pragma once
class A_or_B;
class B {
    A_or_B foo();
};

// A_or_B.hpp
#include "A.hpp"
#include "B.hpp"

class A_or_B : public std::variant<A, B> {
public:
    using base = std::variant<A, B>;
    using base::base;
    using base::operator=;
};
0赞 chrysante 7/19/2023 #2

您可以定义为 typedef for 而不定义 和 。只有实例化类型的对象,才需要定义。A_or_Bstd::variant<A, B>ABA_or_B

// A.hpp
#include "A_or_B_fwd.hpp"

class A {
    A_or_B foo();
};

// B.hpp
#include "A_or_B_fwd.hpp"

class B {
    A_or_B foo();
};

// A_or_B_fwd.hpp
#include <variant>

class A;
class B;

using A_or_B = std::variant<A, B>;

// A_or_B.hpp
#include "A.hpp"
#include "B.hpp"
#include "A_or_B_fwd.hpp"

// A.cpp
#include "A.hpp"
#include "A_or_B.hpp"

A_or_B A::foo() { /* ... */ }

// B.cpp analogously

当正向声明比 更复杂时,使用“-fwd.hpp”样式的标头是合理的 标准库也使用 .class X;<iosfwd>

评论

0赞 Eyal Kamitchi 7/19/2023
和 A.cpp 将具有: 1. 包括 A.hpp 2.包括 A_or_B.hpp 3。定义。右?
0赞 user7860670 7/19/2023
“你可以将 A_or_B 定义为 std::variant<A, B> 的 typedef,而无需定义 A 和 B” - 你不能 stackoverflow.com/a/57226751/7860670
1赞 chrysante 7/19/2023
@user7860670 你绝对可以。在您链接的问题中,确实是不完整的实例化位置。这里不需要这种情况。RectShape
0赞 chrysante 7/19/2023
@EyalKamitchi 是,然后在各自的.cpp文件中定义 和 。A::foo()B::foo