C++20中如何结合std::variant与指针处理对象生命周期?
用std::variant实现2D形状与射线相交检测的C++20方案
针对你想用std::variant替代继承实现运行时多态的需求,这里提供一个符合C++ idiom的方案,解决指针转换和生命周期问题:
核心思路
放弃指针变体的冗余维护,采用值语义的std::variant存储形状,通过std::visit自动完成类型分发,配合重载的相交检测函数实现多态逻辑。同时用std::variant存储相交结果,保留具体类型的引用信息。
代码实现
1. 定义基础类型
#include <variant> #include <cmath> #include <cstdio> #include <limits> #include <type_traits> // 射线类型 struct Ray { float origin_x, origin_y; float dir_x, dir_y; }; // 具体形状类型 struct Circle { float x, y; float radius; }; struct Square { float x, y; // 中心点坐标 float side_length; }; // 形状变体:用值语义存储,避免指针生命周期问题 using Shape = std::variant<Circle, Square>;
2. 定义相交结果类型
用std::variant存储不同形状的相交结果,直接引用原形状对象(避免拷贝):
struct CircleIntersection { const Circle& circle; float distance; float hit_x, hit_y; }; struct SquareIntersection { const Square& square; float distance; float hit_x, hit_y; }; // 相交结果:std::monostate表示无相交 using Intersection = std::variant<std::monostate, CircleIntersection, SquareIntersection>;
3. 实现重载的相交检测函数
为每种形状与射线的组合实现具体的相交逻辑:
Intersection intersect(const Ray& ray, const Circle& circle) { const float dx = circle.x - ray.origin_x; const float dy = circle.y - ray.origin_y; const float a = ray.dir_x * ray.dir_x + ray.dir_y * ray.dir_y; const float b = 2 * (dx * ray.dir_x + dy * ray.dir_y); const float c = dx*dx + dy*dy - circle.radius*circle.radius; const float discriminant = b*b - 4*a*c; if (discriminant < 0 || a == 0) { return std::monostate{}; } const float sqrt_d = std::sqrt(discriminant); float t = (-b - sqrt_d) / (2*a); if (t < 0) { t = (-b + sqrt_d) / (2*a); if (t < 0) return std::monostate{}; } return CircleIntersection{ .circle = circle, .distance = t, .hit_x = ray.origin_x + t * ray.dir_x, .hit_y = ray.origin_y + t * ray.dir_y }; } Intersection intersect(const Ray& ray, const Square& square) { const float half_side = square.side_length / 2; const float min_x = square.x - half_side; const float max_x = square.x + half_side; const float min_y = square.y - half_side; const float max_y = square.y + half_side; float t_near = -std::numeric_limits<float>::infinity(); float t_far = std::numeric_limits<float>::infinity(); if (ray.dir_x != 0) { const float t1 = (min_x - ray.origin_x) / ray.dir_x; const float t2 = (max_x - ray.origin_x) / ray.dir_x; t_near = std::max(t_near, std::min(t1, t2)); t_far = std::min(t_far, std::max(t1, t2)); } if (ray.dir_y != 0) { const float t1 = (min_y - ray.origin_y) / ray.dir_y; const float t2 = (max_y - ray.origin_y) / ray.dir_y; t_near = std::max(t_near, std::min(t1, t2)); t_far = std::min(t_far, std::max(t1, t2)); } if (t_far < 0 || t_near > t_far) { return std::monostate{}; } const float t = (t_near >= 0) ? t_near : t_far; return SquareIntersection{ .square = square, .distance = t, .hit_x = ray.origin_x + t * ray.dir_x, .hit_y = ray.origin_y + t * ray.dir_y }; }
4. 实现变体类型的相交分发
用std::visit自动匹配对应的相交函数:
Intersection intersect(const Ray& ray, const Shape& shape) { return std::visit([&ray](const auto& concrete_shape) { return intersect(ray, concrete_shape); }, shape); }
5. 使用示例
int main() { const Ray ray{0.0f, 0.0f, 1.0f, 0.0f}; // 沿X轴正方向的射线 const Shape shapes[] = { Circle{5.0f, 0.0f, 1.0f}, Square{10.0f, 0.0f, 2.0f}, Circle{1.0f, 3.0f, 0.5f} // 无相交的圆 }; for (const auto& shape : shapes) { const auto intersection = intersect(ray, shape); std::visit([](const auto& isect) { using T = std::decay_t<decltype(isect)>; if constexpr (std::is_same_v<T, std::monostate>) { printf("No intersection\n"); } else if constexpr (std::is_same_v<T, CircleIntersection>) { printf("Intersected Circle at (%.2f, %.2f), distance %.2f\n", isect.hit_x, isect.hit_y, isect.distance); } else if constexpr (std::is_same_v<T, SquareIntersection>) { printf("Intersected Square at (%.2f, %.2f), distance %.2f\n", isect.hit_x, isect.hit_y, isect.distance); } }, intersection); } return 0; }
方案优势
- 无冗余类型维护:不需要同时维护值变体和指针变体,用单一的
Shape变体即可。 - 避免生命周期问题:值语义的变体存储形状,只要
Shape实例的生命周期长于Intersection,引用就安全。如果需要动态创建形状,可改用std::variant<std::unique_ptr<Circle>, std::unique_ptr<Square>>,同样通过std::visit解引用处理。 - 符合C++ idiom:利用
std::visit和重载函数实现类型分发,替代虚函数的多态逻辑,编译期检查类型匹配,运行时开销低。 - 保留类型信息:相交结果用
std::variant存储,可直接访问具体形状的属性,无需向下转型。
内容的提问来源于stack exchange,提问作者davidA
相关产品推荐
相关产品推荐

