CRTP、容器与指针的未定义行为问题排查及修复请求
问题描述
实现了如下CRTP基类:
template <class Child> class Container { friend Child; Container() {}; public: decltype(auto) begin() { return static_cast<const Child&>(*this).abegin(); } decltype(auto) end() { return static_cast<Child&>(*this).aend(); } decltype(auto) begin() const { return static_cast<const Child&>(*this).abegin(); } decltype(auto) end() const { return static_cast<const Child&>(*this).aend(); } };
并让Array类继承该基类:
template<typename T, size_t N> class Array: public Container<Array<T, N>> { public: T array[N]; public: using iterator = zero::iterator::input_iter<T>; using const_iterator = zero::iterator::input_iter<const T>; // Iterator stuff iterator abegin() { return iterator(&array[0]); } iterator aend() { return iterator(&array[N]); } constexpr const_iterator abegin() const { return const_iterator(&array[0]); } constexpr const_iterator aend() const { return const_iterator(&array[N]); } /** * @brief returns the number of elements stored in the underlying array */ [[nodiscard]] inline consteval int size() const noexcept { return N; } template <typename... InitValues> Array(InitValues... init_values) : array{ init_values... } {} // code goes on...
测试代码如下:
import std; import zero; import collections; import iterator; import container; import type_info; using namespace zero; int main() { constexpr collections::Array a = collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L}; Container b = collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L}; std::cout << "Iterating over the values of a constexpr zero::collection!" << std::endl; std::cout << "decltype a: " << zero::types::type_name<decltype(a)>() << std::endl; for (long value : a) std::cout << " - [constexpr] Value: " << value << std::endl; std::cout << "Iterating over the values of a zero::container!" << std::endl; std::cout << "decltype a: " << zero::types::type_name<decltype(b)>() << std::endl; for (long value : b) std::cout << " - Value: " << value << std::endl; return 0; }
运行结果出现未定义行为:
Iterating over the values of a constexpr zero::collection! decltype a: const zero::collections::Array<long, 5> - [constexpr] Value: 1 - [constexpr] Value: 2 - [constexpr] Value: 3 - [constexpr] Value: 4 - [constexpr] Value: 5 Iterating over the values of a zero::container! decltype a: zero::Container<zero::collections::Array<long, 5>> - Value: 8 - Value: 0 - Value: 1 - Value: 2 - Value: 3
需求是将Container作为接口类型用于类型定义、函数参数等场景,无需动态多态,仅依赖CRTP特性。
问题定位
- 对象切片导致核心数据丢失:
Container b = collections::Array<long,5>{...}语句中,Array对象被切片为基类Container对象,仅保留了基类的部分,Array的核心成员array[N]完全丢失。 - 非法向下转型触发UB:CRTP基类的成员函数中,通过
static_cast<const Child&>(*this)将自身转型为子类Array的引用,但此时*this实际是Container类型对象,并非Array对象,这种非法转型直接导致访问了内存中不存在的array数组,触发未定义行为。 - 基类函数的转型错误:非const版本的
begin()函数错误地将*this转换为const Child&,违背了非const成员函数的语义。
修复方案
1. 避免对象切片:使用引用/指针替代值语义
要将Container作为接口类型,必须用引用或指针保留完整的子类对象,禁止直接用基类值类型接收子类对象:
// 使用左值引用(需保证原对象生命周期足够) auto arr = collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L}; Container<collections::Array<long,5>>& b = arr; // 或使用智能指针管理对象 auto arr_ptr = std::make_unique<collections::Array<long,5>>(1L,2L,3L,4L,5L); Container<collections::Array<long,5>>* b_ptr = arr_ptr.get();
2. 修正CRTP基类的转型错误
调整非const版本begin()的转型目标为非const子类引用:
template <class Child> class Container { friend Child; Container() = default; public: // 非const版本转成非const Child&,匹配语义 decltype(auto) begin() { return static_cast<Child&>(*this).abegin(); } decltype(auto) end() { return static_cast<Child&>(*this).aend(); } // const版本保持不变 decltype(auto) begin() const { return static_cast<const Child&>(*this).abegin(); } decltype(auto) end() const { return static_cast<const Child&>(*this).aend(); } };
3. 可选:禁用基类拷贝/移动,从根源防止切片
在基类中禁用拷贝构造、拷贝赋值(可选禁用移动构造和赋值),强制用户使用引用/指针,避免意外切片:
template <class Child> class Container { friend Child; Container() = default; // 禁用拷贝构造与赋值 Container(const Container&) = delete; Container& operator=(const Container&) = delete; // 可选:禁用移动构造与赋值 Container(Container&&) = delete; Container& operator=(Container&&) = delete; public: decltype(auto) begin() { return static_cast<Child&>(*this).abegin(); } decltype(auto) end() { return static_cast<Child&>(*this).aend(); } decltype(auto) begin() const { return static_cast<const Child&>(*this).abegin(); } decltype(auto) end() const { return static_cast<const Child&>(*this).aend(); } };
内容的提问来源于stack exchange,提问作者Alex Vergara
相关产品推荐
相关产品推荐

