【问题标题】:C++: object properties emulation: thoughtsC++:对象属性模拟:想法
【发布时间】:2012-02-29 09:15:54
【问题描述】:

大家好!

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

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

is property 是私有数据成员。公共数据成员违反 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. 您如何看待从主题地址计算所有者对象地址?您知道哪些技巧和可能的陷阱?

请告诉我们你的想法!

谢谢!

【问题讨论】:

  • 我不太明白。公共数据成员有什么问题?
  • 幸运的是,在惯用的 C++ 中,您经常会发现“属性”和 getter/setter 都不像您想象的那样必要。
  • 我仍然震惊和恐惧地盯着&amp;(reinterpret_cast&lt;typename C*&gt;(base)-&gt;*member_ptr)
  • @pmr 除了设置实际值之外,没有在 set 方法中做过其他事情吗?
  • @DaddyM 未定义的行为。 stackoverflow.com/questions/2397984/… 举了几个例子,很好地解释了这个词的含义。

标签: c++ properties emulation object-properties


【解决方案1】:

由于一些明显的原因,代码无法编译。大部分的 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;
};

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

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

【讨论】:

  • @DaddyM 如果你这么说。我不会和你争论。我给了你一个你发布的混乱的编译,更干净的版本,并指出了它的缺陷。用它来打自己的脚仍然取决于你。
  • 冷静下来。感谢您的帮助。
  • 当数据曾经在公共数据成员中时,这很有用,但后来更改为需要 getter 和 setter 以便某些代码在设置时可以执行某些操作。我认为这些属性仿真没有更频繁地使用很奇怪——我发现它比 getter 和 setter 更具可读性。属性模拟类似于 std::vector&lt;bool&gt; 使用的虚假引用之类的东西
  • @qbt937 这是我第一次听到有人提到vector&lt;bool&gt;,因为这是件好事。
  • @pmr 这是一件好事。能够通过运算符重载做到这一点是 c++ 比 java 好得多的原因之一。重载operator==operator[] 可以使代码更具可读性。
【解决方案2】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-03
    相关资源
    最近更新 更多