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

如何利用C++23的推导this结合CRTP管理集合中的多态行为

如何利用C++23的推导this结合CRTP管理集合中的多态行为

嘿,这个需求我之前也折腾过!C++23的推导this(就是带this auto&&的成员函数)确实给CRTP静态多态注入了新活力,既能保留静态绑定的性能优势,又能优雅地管理不同派生类的集合,咱们一步步来拆解怎么实现。

先搞懂推导this给CRTP带来的核心便利

以前写CRTP,基类里总得写个static_cast<Derived&>(*this)来转成派生类实例,不仅代码啰嗦,还容易写错模板参数。现在有了推导this,基类的成员函数可以直接用this auto&& self作为参数,编译器会自动把self推导成当前调用对象的实际类型——也就是你的派生类实例,完全不用手动转换,代码清爽多了,甚至还能把CRTP基类做成非模板类,进一步降低复杂度。

举个最基础的CRTP+推导this的例子:

// 非模板的CRTP基类!靠推导this自动适配所有派生类
struct ShapeCRTP {
    // 用this auto&& self获取派生类实例
    void draw(this auto&& self) const {
        // 直接调用派生类的具体实现
        self.draw_impl();
    }

    double calculate_area(this auto&& self) const {
        return self.area_impl();
    }
};

// 派生类:圆形
struct Circle : ShapeCRTP {
    double radius;
    explicit Circle(double r) : radius(r) {}

    // 只需要实现具体业务逻辑
    void draw_impl() const {
        std::cout << "画一个半径为" << radius << "的圆\n";
    }

    double area_impl() const {
        return 3.14159 * radius * radius;
    }
};

// 派生类:正方形
struct Square : ShapeCRTP {
    double side;
    explicit Square(double s) : side(s) {}

    void draw_impl() const {
        std::cout << "画一个边长为" << side << "的正方形\n";
    }

    double area_impl() const {
        return side * side;
    }
};

看,派生类只需要专注实现业务逻辑draw_impl和area_impl,CRTP基类的通用方法自动转发,完全不用管类型转换那堆事,比传统CRTP省心太多。

方案1:用std::variant做静态分发的集合(无动态分配、无虚函数开销)

如果你的场景里所有派生类类型都是已知的,那用std::variant来存不同的派生类是最优解——没有动态内存分配,没有虚函数调用开销,完全是编译期静态绑定,性能拉满。

直接上完整代码:

#include <vector>
#include <variant>
#include <iostream>
#include <utility>

struct ShapeCRTP {
    void draw(this auto&& self) const {
        self.draw_impl();
    }

    double calculate_area(this auto&& self) const {
        return self.area_impl();
    }
};

struct Circle : ShapeCRTP {
    double radius;
    explicit Circle(double r) : radius(r) {}
    void draw_impl() const { std::cout << "圆形(r=" << radius << ")\n"; }
    double area_impl() const { return 3.14159 * radius * radius; }
};

struct Square : ShapeCRTP {
    double side;
    explicit Square(double s) : side(s) {}
    void draw_impl() const { std::cout << "正方形(s=" << side << ")\n"; }
    double area_impl() const { return side * side; }
};

// 定义variant类型,包含所有可能的形状
using Shape = std::variant<Circle, Square>;

// 通用的访问工具函数,用std::visit做编译期分发
template<typename Func>
void process_shape(const Shape& shape, Func&& func) {
    std::visit(std::forward<Func>(func), shape);
}

int main() {
    // 用vector存Shape(其实是variant)
    std::vector<Shape> shapes;
    shapes.emplace_back(Circle(2.0));
    shapes.emplace_back(Square(3.0));

    // 遍历处理每个形状
    for (const auto& shape : shapes) {
        process_shape(shape, [](const auto& s) {
            s.draw();
            std::cout << "面积:" << s.calculate_area() << "\n";
        });
    }
}

关键说明

  • std::variant是类型安全的联合体,所有派生类都存在栈上(如果对象不大的话),没有堆分配开销。
  • std::visit会在编译期生成所有可能的分支,完全是静态绑定,性能和直接调用派生类方法一样。
  • 推导this在这里的作用就是让CRTP基类的方法能无缝适配variant里的任意派生类,不用写一堆重载。

方案2:用类型擦除做灵活分发(支持未知派生类)

如果你的场景里无法提前知道所有派生类类型,那可以用类型擦除来做一个通用的包装类,兼顾灵活性和性能——虽然有一次虚函数调用和动态分配,但比传统的虚函数继承更灵活,因为派生类只需要实现CRTP要求的impl方法,不用直接继承虚基类。

完整代码示例:

#include <vector>
#include <memory>
#include <iostream>
#include <utility>
#include <type_traits>

// 非模板CRTP基类
struct ShapeCRTP {
    void draw(this auto&& self) const {
        self.draw_impl();
    }

    double calculate_area(this auto&& self) const {
        return self.area_impl();
    }
};

// 类型擦除的包装类,作为集合的公共接口
class Shape {
    // 内部的概念类,定义多态接口
    struct Concept {
        virtual ~Concept() = default;
        virtual void draw() const = 0;
        virtual double calculate_area() const = 0;
    };

    // 模型类,包装具体的派生类
    template<typename T>
    struct Model final : Concept {
        T value;
        template<typename U>
        explicit Model(U&& v) : value(std::forward<U>(v)) {}

        void draw() const override {
            // 调用CRTP基类的draw方法,自动转发到派生类的impl
            value.draw();
        }

        double calculate_area() const override {
            return value.calculate_area();
        }
    };

    std::unique_ptr<Concept> pimpl_;
public:
    // 构造函数,接受任意CRTP派生类
    template<typename T, std::derived_from<ShapeCRTP> D = std::decay_t<T>>
    explicit Shape(T&& t) : pimpl_(std::make_unique<Model<D>>(std::forward<T>(t))) {}

    // 转发到pimpl的方法
    void draw() const { pimpl_->draw(); }
    double calculate_area() const { return pimpl_->calculate_area(); }
};

// 派生类1:圆形
struct Circle : ShapeCRTP {
    double radius;
    explicit Circle(double r) : radius(r) {}
    void draw_impl() const { std::cout << "圆形(r=" << radius << ")\n"; }
    double area_impl() const { return 3.14159 * radius * radius; }
};

// 派生类2:正方形
struct Square : ShapeCRTP {
    double side;
    explicit Square(double s) : side(s) {}
    void draw_impl() const { std::cout << "正方形(s=" << side << ")\n"; }
    double area_impl() const { return side * side; }
};

// 可以随时加新的派生类,不用修改Shape类
struct Triangle : ShapeCRTP {
    double base, height;
    Triangle(double b, double h) : base(b), height(h) {}
    void draw_impl() const { std::cout << "三角形(b=" << base << ", h=" << height << ")\n"; }
    double area_impl() const { return 0.5 * base * height; }
};

int main() {
    std::vector<Shape> shapes;
    shapes.emplace_back(Circle(2.0));
    shapes.emplace_back(Square(3.0));
    shapes.emplace_back(Triangle(4.0, 5.0));

    for (const auto& shape : shapes) {
        shape.draw();
        std::cout << "面积:" << shape.calculate_area() << "\n";
    }
}

关键说明

  • Shape类是类型擦除的包装,对外提供统一的接口,内部用pimpl指向具体的Model实例。
  • 新的派生类只需要继承ShapeCRTP并实现impl方法,不用修改Shape类,完全符合开闭原则。
  • 推导this让CRTP基类的实现极其简洁,没有任何类型转换的冗余代码。

总结一下两种方案的适用场景

  • 用variant的静态分发:适合提前知道所有派生类的场景,性能最优,无动态分配,完全静态绑定。
  • 用类型擦除的灵活分发:适合无法提前枚举所有派生类的场景,灵活性高,性能略逊于静态分发,但比传统虚函数继承更易用。

不管哪种方案,C++23的推导this都让CRTP的实现变得更简洁、更安全,再也不用写那些容易出错的static_cast了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 16:18:06