代码之家  ›  专栏  ›  技术社区  ›  n. m. could be an AI

具有数据成员语法的零成本属性

  •  18
  • n. m. could be an AI  · 技术社区  · 6 年前

    我有(?)发明了使用数据成员语法实现零成本属性的方法。我的意思是用户可以写:

    some_struct.some_member = var;
    var = some_struct.some_member;
    

    这些成员访问重定向到零开销的成员函数。

    虽然最初的测试表明该方法在实践中确实有效,但我还不能确定它是否没有未定义的行为。下面是演示该方法的简化代码:

    template <class Owner, class Type, Type& (Owner::*accessor)()>
    struct property {
        operator Type&() {
            Owner* optr = reinterpret_cast<Owner*>(this);
            return (optr->*accessor)();
        }
        Type& operator= (const Type& t) {
            Owner* optr = reinterpret_cast<Owner*>(this);
            return (optr->*accessor)() = t;
        }
    };
    
    union Point
    {
        int& get_x() { return xy[0]; }
        int& get_y() { return xy[1]; }
        std::array<int, 2> xy;
        property<Point, int, &Point::get_x> x;
        property<Point, int, &Point::get_y> y;
    };
    

    测试驱动程序证明了该方法是有效的,而且它确实是零成本的(属性不占用额外的内存):

    int main()
    {
        Point m;
        m.x = 42;
        m.y = -1;
    
        std::cout << m.xy[0] << " " << m.xy[1] << "\n";
        std::cout << sizeof(m) << " " << sizeof(m.x) << "\n";
    }
    

    真正的代码有点复杂,但方法的要点在这里。它基于使用真实数据的联合( xy 在本例中)和空属性对象。(真实数据必须是标准的布局类才能工作)。

    联合是必需的,因为否则属性不必要地占用内存,尽管是空的。

    为什么我认为这里没有UB?该标准允许访问标准布局联合成员的公共初始序列。这里,常见的初始序列是空的。数据成员 x y 根本无法访问,因为没有数据成员。我对标准的理解表明这是允许的。 reinterpret_cast 应该可以,因为我们正在将一个联合成员强制转换为它的包含联合,而这些是指针可相互转换的。

    这是标准允许的吗,还是我这里缺少一些UB?

    2 回复  |  直到 6 年前
        1
  •  14
  •   Nicol Bolas    6 年前

    我是乌布医生。

    [basic.life]

    同样,在对象的生存期开始之前,但在分配了对象将占用的存储之后,或在对象的生存期结束之后,在重用或释放了对象所占用的存储之前,可以使用任何引用原始对象的glvalue,但只能以有限的方式使用。有关正在构造或销毁的对象,请参见[class.cdtor]。否则,这样的glvalue引用分配的存储,并且使用不依赖于其值的glvalue的属性是定义良好的。程序具有未定义的行为,如果:…]

    • glvalue用于调用对象的非静态成员函数,或者

    通过 definition ,联盟的非活动成员不在其生存期内。


    一种可能的解决方法是使用C++ 20。 [[no_unique_address]]

    struct Point
    {
        int& get_x() { return xy[0]; }
        int& get_y() { return xy[1]; }
        [[no_unique_address]] property<Point, int, &Point::get_x> x;
        [[no_unique_address]] property<Point, int, &Point::get_y> y;
        std::array<int, 2> xy;
    };
    
    static_assert(offsetof(Point, x) == 0 && offsetof(Point, y) == 0);
    
        2
  •  6
  •   Nicol Bolas    6 年前

    这是什么 common-initial-sequence rule says about unions :

    在具有结构类型的活动成员的标准布局联合中 T1 ,允许读取非静态数据成员 m 结构类型的另一个联合成员 T2 假设m是T1和T2的共同初始序列的一部分,其行为就好像指定了T1的相应成员。

    您的代码不符合条件。为什么?因为你不是 阅读 来自“另一个工会成员”。你正在做 m.x = 42; . 这不是在读;这是在调用另一个联合成员的成员函数。

    所以它不符合一般的初始序列规则。如果没有通用的初始序列规则来保护您,那么访问联合的非活动成员就是ub。