这可能是一个很长的阅读,提前抱歉。这是我一直在从事的个人编译器项目。采用 EBNF 语法并将其编译为 C++、flex 和 bison 代码,该代码会自动为所描述的语法构建 AST。
我使用继承来对生成的 AST 进行建模,这导致我使用指针。我正在开发一个不可为空的智能指针类,其继承类似于 AST。
例如,如果我有这个 AST 继承模型:
Node
├── Terminal
└── Nonterminal
├── Expression
│ ├── Operator
│ │ ├── Binary
│ │ │ ├── Add
│ │ │ └── Multiply
│ │ └── Unary
│ └── FunctionCall
└── Statement
└── Conditional
├── Loop
│ ├── For
│ └── While
├── Select
└── If
智能指针类继承模型如下所示:
Pointer<Node>
├── Pointer<Terminal>
└── Pointer<Nonterminal>
├── Pointer<Expression>
│ ├── Pointer<Operator>
│ │ ├── Pointer<Binary>
│ │ │ ├── Pointer<Add>
│ │ │ └── Pointer<Multiply>
│ │ └── Pointer<Unary>
│ └── Pointer<FunctionCall>
└── Pointer<Statement>
└── Pointer<Conditional>
├── Pointer<Loop>
│ ├── Pointer<For>
│ └── Pointer<While>
├── Pointer<Select>
└── Pointer<If>
我的目标是为每种类型的 AST 节点提供一个虚拟克隆方法,该方法返回指向其自身类型的智能指针。我想使用这些方法来制作复制构造函数。二进制表达式(例如
Add
)将有两个操作数,两者都是 Pointer<Expression>
。为了定义 Add
的复制构造函数,我需要能够复制虚拟绑定的 Expression
,或任何与此相关的 Node
。类 Node
将定义一个虚拟方法,如下所示:
class Node {
public:
virtual Pointer<Node> clone() const = 0;
};
它将像这样被覆盖:
class Token : public Node {
public:
Pointer<Token> clone() override;
};
所以我可以这样做:
int main() {
Pointer<Token> token = Pointer<Token>::New();
Pointer<Node> tokenDynamic = new Pointer<Token>::New();
Pointer<Token> tokenClone = token.clone();
Pointer<Node> tokenDynamicClone = tokenDynamic.clone();
}
我遇到了一个问题。
我已经尝试尽可能简化这个例子,但我仍然无法让它工作:
#include <memory>
#include <tr2/type_traits>
class Base;
class Derived;
template<typename T>
class Template : public Template<typename T::Parent> {};
template<>
class Template<Base> {};
class Base {
public:
virtual std::string name() const { return "Node"; }
virtual Template<Base> clone() const = 0;
};
class Derived : public Base {
public:
using Parent = Base;
std::string name() const { return "Token"; }
Template<Derived> clone() const override { return Template<Derived>(); }
};
template<typename T>
using base_t = typename std::tr2::bases<T>::type::first::type;
int main()
{
auto base = Template<Base>();
auto derived = Template<Derived>();
base = Template<Derived>();
static_assert(std::is_same_v<Template<Base>, base_t<Template<Derived>>>);
}
我认为虚函数可以被协变/缩小返回类型覆盖。上面代码中的
static_assert
并没有失败,这意味着Template<Derived>
确实继承了Template<Base>
。但是,编译器失败并显示以下消息:
./test2.cpp:24:21: error: invalid covariant return type for ‘virtual Template<Derived> Derived::clone() const’
24 | Template<Derived> clone() const override { return Template<Derived>(); }
| ^~~~~
./test2.cpp:16:26: note: overridden function is ‘virtual Template<Base> Base::clone() const’
16 | virtual Template<Base> clone() const = 0;
| ^~~~~
我已经无计可施了。如果有人能指出我正确的方向,我将不胜感激。感谢您阅读本文!
只有返回(内置)指针或引用的函数才可能具有协变返回类型。
[class.virtual] 如果函数 D::f 覆盖函数 B::f,则返回类型 如果满足以下条件,则函数是协变的:
- (8.1) — 两者都是指向类的指针,两者都是对类的左值引用,或者两者都是对类的右值引用 课程
然而,并非一切都丢失了。可以使用非虚拟接口惯用法来模拟协方差。该技术在几个 SO 问题和答案中都有描述,因此我不会在这里重复。有关更多信息,请参阅此。
在一个不相关的注释中,让智能指针相互继承可能不是一个好主意。如果您只需要协变返回类型,则可以安全地摆脱这种继承,甚至可能摆脱整个智能指针模板,并使用
std::shared_ptr
或 std::unique_ptr
代替。