1

今天是个好日子!

属性未在 C++ 中实现。我估计我们不能写

myObject.property = value;   // try to set field f_ to value

isproperty是私有数据成员。公共数据成员违反 OOP 封装规则。

相反,我们必须编写 getter/setter 代码:

myObject.setField(value);    // try to set field f_ to value

这篇文章是关于C++ 中的属性仿真。属性调用看起来像公共数据成员调用,但自定义 UDF 代码用于设置或获取真正的私有数据成员值。

我们想做的是允许简单的接口使用(像公共数据成员,没有函数调用语法),而底层的 getter/setter 可能很复杂(不仅仅是数据分配/读取)

下面的代码(我的同事写的)展示了简单的属性实现:

#include <iostream>

template<class C, class M>
inline C* get_parent_this(typename M C::*member_ptr, M*const member_this) 
{
    //interpret 0 as address of parent object C and find address of its member
    char* base   = reinterpret_cast<char*>(nullptr);
    char* member = reinterpret_cast<char*>( &(reinterpret_cast<typename C*>(base)->*member_ptr) );
    //modify member_this with offset = (member - base)
    return reinterpret_cast<typename C*>(reinterpret_cast<char*>(member_this) - (member - base) );
}

class Owner
{
    int x,pr_y;

    int   get_y()       {return pr_y;}
    void  set_y(int v)  {pr_y = v;}
public:
    struct
    {
        operator int()          { return get_parent_this(&Owner::y, this)->get_y(); }
        void operator =(int v)  { get_parent_this(&Owner::y, this)->set_y(v); } 
    } y;
};

int main ()
{   
    Owner ow;
    ow.y = 5;
    std::cout << ow.y;

    if( get_parent_this(&Owner::y, &ow.y) == &ow) std::cout << "OK\n";
    if( (char *)&ow.y != (char *)&ow) std::cout << "OK\n";

    return 0;
}

在上面的示例中有一个简单的 getter/setter 对,它不做任何额外的工作(例如完整性检查或边界检查),但此代码可能是复杂的执行边界检查、完整性检查等。这只是测试示例。

不要担心get_parent_this帮助模板中的“奇怪”代码。最“可怕”的事情是偏移计算。nullptr (NULL, 0x0) 地址用作存根。我们不会从该地址写入或读取。我们仅将它用于基于子对象地址的所有者对象偏移量计算。我们可以使用任何地址代替 0x0。所以,这没有意义。


属性用法:

  1. 如果有人使用公共数据成员属性在以下情况下可能会有所帮助:1.1。如果出现错误,则跟踪公共数据成员调用;1.2. 轻松升级基于公共数据成员使用的遗留代码;

  1. 您如何看待 C++ 中的属性仿真?是活泼的想法吗?这个想法有缺点吗(请显示)?
  2. 您如何看待从子对象地址计算所有者对象地址?你知道哪些技巧和可能的陷阱?

请告诉我们你的想法!

谢谢!

4

2 回答 2

2

由于某些明显的原因,代码无法编译。大多数 typenameS是不必要的。我想nullptr是一些自制软件 #define(肯定不是 C++11 nullptr)。

这是一个经过美化的编译版本,可以更轻松地查看实际发生的情况:

#include <iostream>

template<class C, class M>
inline C* get_parent_this(M C::*member_ptr, M* const member_this) 
{
  C* base = NULL;
  // !!! this is the tricky bit !!!
  char* member = reinterpret_cast<char*>(&(base->*member_ptr));
  return reinterpret_cast<C*>(reinterpret_cast<char*>(member_this) - member );
}

class Owner
{
  int x, pr_y;
  virtual int   get_y()       {return pr_y;}
  void  set_y(int v)  {pr_y = v;}
public:
  struct
  {
    operator int()          { return get_parent_this(&Owner::y, this)->get_y(); }
    void operator =(int v)  { get_parent_this(&Owner::y, this)->set_y(v); } 
  } y;
};

棘手的一点:这涉及到空指针的取消引用。这在某种程度上等同于在某些编译器中过去(现在仍然是)定义宏的offsetof方式stddef.h。这在某些编译器上可靠地工作,但未定义的行为。以下是有关该主题的讨论的一些链接:

我认为不值得在这里重复讨论。除了混淆的公共数据之外,我真的看不出代码给你带来了什么。对于您真正需要的少数情况,setter我只会编写它而不是使用它。如果您的编码风格出于特殊原因禁止公开数据,请编写一组宏来定义它们。

于 2012-02-07T23:09:03.113 回答
2

参考对象已经是一罐蠕虫。问题包括:

  • 它们会干扰类型推断,例如y = max(y, myobject.y)
  • 你不能重载operator.,所以像这样代理一个类是相当麻烦的
  • 惊喜之类的swap(object1.y, object2.y)

你使用它的方式有额外的惊喜。例如,如果有人想调用你的 getter 的副作用,那么简单地编写myobject.y;是行不通的:他们实际上必须调用对 int 的强制转换。

我无法想象除了“不要这样做”之外你会得到任何建议,除非你能很好地解释为什么你认为你需要模拟属性。(即便如此,该建议也可能倾向于解释为什么你真的不需要它)

于 2012-02-08T07:29:13.333 回答