C++容器存储多派生类对象问题:光线追踪器Scene类实现受阻
首先纠正一个误解:你用std::unique_ptr<Object>存储派生类对象不会触发对象切片——对象切片只发生在直接用基类对象存储派生类实例的场景(比如Object obj = Sphere(...)),指针/引用语义下不存在切片问题。你遇到的编译错误,是因为编译器只知道objects[0]的静态类型是Object*,而Object类本身没有radius成员。
针对光线追踪器的场景,推荐以下两种解决方案:
方案一:基于多态的接口设计(首选)
光线追踪中,所有物体都需要实现统一的核心逻辑(比如判断光线是否与物体相交、获取表面颜色等),因此最合理的方式是在基类中定义纯虚接口,派生类各自实现这些接口。这样你不需要直接访问派生类的特有成员,而是通过基类接口完成所有操作。
修改你的代码示例:
#include <iostream> #include <vector> #include <memory> #include <cmath> struct color_t { unsigned char r, g, b, a; }; struct vec3d { double x, y, z; // 重载减法运算符,方便光线相交计算 vec3d operator-(const vec3d& other) const { return {x - other.x, y - other.y, z - other.z}; } }; class Object { public: vec3d position; // 纯虚函数,派生类必须实现 virtual color_t get_color() const = 0; // 光线追踪核心接口:判断光线与物体的相交,返回是否相交,t为光线到交点的距离 virtual bool intersect(const vec3d& ray_origin, const vec3d& ray_dir, double& t) const = 0; // 虚析构函数,确保派生类对象能正确析构 virtual ~Object() = default; }; class Sphere : public Object { public: Sphere(vec3d center, double radius, color_t color) : radius(radius), color(color) { this->position = center; } // 实现基类的纯虚函数 color_t get_color() const override { return color; } bool intersect(const vec3d& ray_origin, const vec3d& ray_dir, double& t) const override { vec3d oc = ray_origin - position; double a = ray_dir.x*ray_dir.x + ray_dir.y*ray_dir.y + ray_dir.z*ray_dir.z; double b = 2.0 * (oc.x*ray_dir.x + oc.y*ray_dir.y + oc.z*ray_dir.z); double c = oc.x*oc.x + oc.y*oc.y + oc.z*oc.z - radius*radius; double discriminant = b*b - 4*a*c; if (discriminant < 0) return false; t = (-b - sqrt(discriminant)) / (2.0*a); return t > 0; } double radius; color_t color; }; int main() { auto p_sphere = std::make_unique<Sphere>(Sphere({0, 0, 2}, 1, {0, 0, 255, 255})); std::cout << p_sphere->position.x << std::endl; std::cout << p_sphere->radius << std::endl; std::vector<std::unique_ptr<Object>> objects; objects.push_back(std::move(p_sphere)); std::cout << objects[0]->position.x << std::endl; // 通过基类接口访问派生类属性 std::cout << (int)objects[0]->get_color().b << std::endl; }
这种方式完全符合光线追踪的设计逻辑,所有物体通过统一接口交互,不需要关心具体类型,代码扩展性也更好——新增Polygon、Mesh等类型时,只需要继承Object并实现接口即可。
方案二:动态类型转换(仅必要时使用)
如果确实需要直接访问派生类的特有成员,可以使用dynamic_cast进行类型转换,但这种方式会带来运行时开销,且破坏多态的封装性,不推荐在光线追踪的核心逻辑中大量使用。
示例代码(替换main函数中的对应输出部分):
if (auto* sphere_ptr = dynamic_cast<Sphere*>(objects[0].get())) { std::cout << sphere_ptr->radius << std::endl; } else { std::cout << "Not a sphere" << std::endl; }
关于Scene类的设计
你的初始思路是对的:Scene类可以包含std::vector<std::unique_ptr<Object>>和std::vector<std::unique_ptr<Light>>两个容器,分别存储场景物体和光源。光源部分同样可以用多态设计——定义Light基类,派生DirectionalLight、PointLight等,实现统一的接口(比如计算光照贡献)。
内容的提问来源于stack exchange,提问作者Kirill

