You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CRTP模式下C++模板类友元问题及访问者模式实现咨询

CRTP模式下C++模板类友元问题及访问者模式实现咨询

嗨,我看到你想用CRTP来实现访问者模式,目前贴出来的代码里Visitable类的友元部分好像没写完,不过先结合你现有的代码和常见的实现思路来聊聊~

先把你给出的现有代码整理一下:

template <class Derived> 
class Visitor { 
public:     
    Visitor() {         
        static_assert(!std::is_same<Visitor, Derived>::value,                      
                      "Visitor class cannot be instantiated directly. Use a derived class.");     
    }     
    virtual ~Visitor() = default;     
    virtual void visit(Derived&) = 0; 
};  

template <class Derived> 
class Visitable { 
    friend void Visitor // 这里应该是未完成的友元声明

首先说下你现有Visitor类的设计:用static_assert防止直接实例化基类,这个思路是对的,但访问者模式的核心是一个访问者能处理多种不同的被访问对象,而当前的Visitor模板只定义了针对Derived类型的visit方法,显然满足不了多类型处理的需求,得调整一下。

再说说Visitable类的友元问题:CRTP的Visitable通常需要让访问者能访问自己的内部成员(比如私有属性),所以常见的做法是把访问者的基类或者模板声明为友元。结合访问者模式的完整实现,我给你梳理一套可行的方案:

完整实现思路&代码示例

我们需要一个统一的访问者基类来声明所有需要处理的被访问类型的visit方法,再用CRTP模板来约束访问者的实现,同时让Visitable通过CRTP自动给子类生成accept方法。

#include <iostream>
#include <type_traits>

// 第一步:定义访问者基类,声明所有要处理的Visitable类型的visit接口
class VisitorBase {
public:
    virtual ~VisitorBase() = default;
    virtual void visit(class Circle&) = 0;
    virtual void visit(class Square&) = 0;
};

// 第二步:CRTP Visitor模板,继承自VisitorBase,防止直接实例化
template <class Derived>
class Visitor : public VisitorBase {
public:
    Visitor() {
        static_assert(!std::is_same<Visitor, Derived>::value,
                      "Visitor class cannot be instantiated directly. Use a derived class.");
    }
};

// 第三步:CRTP Visitable模板,提供accept方法并设置友元
template <class Derived>
class Visitable {
    // 让所有VisitorBase的子类(也就是具体访问者)都能访问内部成员
    friend class VisitorBase;
protected:
    // 保护构造函数,禁止直接实例化Visitable基类
    Visitable() = default;
    virtual ~Visitable() = default;
public:
    // 自动生成accept方法,把自己强转为Derived类型传给访问者
    void accept(VisitorBase& visitor) {
        visitor.visit(static_cast<Derived&>(*this));
    }
};

// 具体的被访问类:Circle
class Circle : public Visitable<Circle> {
private:
    double radius = 5.0;
public:
    // 如果不需要访问者直接碰私有成员,也可以留getter
    double getRadius() const { return radius; }
};

// 具体的被访问类:Square
class Square : public Visitable<Square> {
private:
    double side = 4.0;
public:
    double getSide() const { return side; }
};

// 具体访问者:打印形状信息
class ShapePrinter : public Visitor<ShapePrinter> {
public:
    void visit(Circle& circle) override {
        std::cout << "Circle with radius: " << circle.getRadius() << std::endl;
    }

    void visit(Square& square) override {
        std::cout << "Square with side: " << square.getSide() << std::endl;
    }
};

// 具体访问者:计算形状面积
class ShapeAreaCalculator : public Visitor<ShapeAreaCalculator> {
public:
    void visit(Circle& circle) override {
        double area = 3.14159 * circle.getRadius() * circle.getRadius();
        std::cout << "Circle area: " << area << std::endl;
    }

    void visit(Square& square) override {
        double area = square.getSide() * square.getSide();
        std::cout << "Square area: " << area << std::endl;
    }
};

// 测试代码
int main() {
    Circle c;
    Square s;
    ShapePrinter printer;
    ShapeAreaCalculator calculator;

    c.accept(printer);
    c.accept(calculator);
    s.accept(printer);
    s.accept(calculator);

    return 0;
}

关键细节说明

  1. VisitorBase的作用:作为统一的接口,让Visitable的accept方法可以接受任何具体访问者,不用关心访问者的具体类型。
  2. CRTP Visitor模板:用static_assert确保用户必须继承它才能创建访问者,避免误用基类。
  3. Visitable的友元声明:friend class VisitorBase让所有继承自VisitorBase的具体访问者都能访问Visitable子类的私有成员(如果你的业务需要直接操作私有属性,这个声明就很有用)。
  4. accept方法的自动生成:通过CRTP的static_cast<Derived&>(*this),把基类指针/引用转换成具体子类类型,传给访问者的visit方法,实现类型匹配。

如果你的需求是针对单个类型的访问者,那可以简化设计,但访问者模式大多是用来处理多类型场景的,上面的方案应该更贴合你的需求~

备注:内容来源于stack exchange,提问作者manau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 16:14:53