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

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特性。

问题定位
  1. 对象切片导致核心数据丢失:Container b = collections::Array<long,5>{...}语句中,Array对象被切片为基类Container对象,仅保留了基类的部分,Array的核心成员array[N]完全丢失。
  2. 非法向下转型触发UB:CRTP基类的成员函数中,通过static_cast<const Child&>(*this)将自身转型为子类Array的引用,但此时*this实际是Container类型对象,并非Array对象,这种非法转型直接导致访问了内存中不存在的array数组,触发未定义行为。
  3. 基类函数的转型错误:非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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 00:00:09