【问题标题】:Any problems with this C++ const reference accessor interface idiom?这个 C++ const 引用访问器接口习语有什么问题吗?
【发布时间】:2011-02-26 12:27:34
【问题描述】:

我正在将一个结构体转换为一个类,这样我就可以为我的变量强制执行一个 setter 接口。
不过,我不想更改读取变量的所有实例。 所以我转换了这个:

struct foo_t {
    int x;
    float y;
};

到这里:

class foo_t {
    int _x;
    float _y;
public:
    foot_t() : x(_x), y(_y) {  set(0, 0.0);  }

    const int &x;
    const float &y;

    set(int x, float y)  {  _x = x;  _y = y;  }
};

我对此很感兴趣,因为它似乎模仿了 C# 的公共只读属性理念。
编译正常,我还没有看到任何问题。

除了在构造函数中关联 const 引用的样板之外,这种方法有什么缺点?
有什么奇怪的混叠问题吗?
为什么我以前没见过这个成语?

【问题讨论】:

  • 为什么是 const int &x;常量浮动 &y;需要吗?
  • @ArunShaha 他在他的问题中解释了这一点。目的是为xy 提供一个“访问器”,它与访问结构中的变量保持相同的语法。
  • C# 和 C++ 是完全不同的语言,有自己的风格和用法。将一种语言的样式复制到另一种语言会带来很多麻烦,学习新语言的最佳技术要简单得多,而不是复制可能不兼容的样式(例如 geter/seter 的可怕概念)
  • 我应该澄清一点——“setter”只是为了强制类的所有内部状态同时更新,而不是因为我有获取/设置每个变量的冲动。在我的特殊情况下,我强制您同时更新时间戳和整数,并尝试通过界面使其显而易见。
  • 虽然我很欣赏你的把戏,但保证以后会出现问题(正如尼尔在他的回答中指出的那样)。考虑使用 C++ 重构工具(我使用 Visual AssistX),它可以帮助您找到 x 和 y 的所有用法并用函数调用替换它们。

标签: c++ constants public accessor


【解决方案1】:

您的方法不灵活。当每个变量都有一个getter / setter 时,这意味着如果你在你的类中添加一些东西,你就不必重写你的set 方法。

这不好,因为你不能有 constnon-const getter (很少使用,但有时可能有用)。

您不能复制引用,因此,您的类变得不可复制。

另外,在你的类中初始化引用意味着额外的内存,如果我们谈论的是,例如,顶点类(虽然我认为它实际上不应该是一个类),这可能成为一场灾难。


[后面的一切都是主观的]

在我看来,getter 和 setter 的目的不是简单的修改,而是封装一系列导致值修改或返回值的操作(我们将其视为 可见结果 )。

在您的示例中,个人结构会更有效,因为它包装 POD 数据并在逻辑上“结构化”它。

【讨论】:

    【解决方案2】:

    因为您公开了对foo_t 内部数据的引用,所以存在一个别名问题,foo_t 对象外部的代码可能会在对象的生命周期之后保留对其数据的引用。考虑:

    foo_t* f = new foo_t();
    const int& x2 = f->x;
    delete f;
    std::cout << x2; // Undefined behavior; x2 refers into a foo_t object that was deleted
    

    或者,更简单:

    const int& x2 = foo_t().x;
    std::cout << x2; // Undefined behvior; x2 refers into a foo_t object that no longer exists
    

    这些并不是特别现实的例子,但是当对象公开或返回对其数据(公共或私有)的引用时,这是一个潜在的问题。当然,在 foo_t 对象本身的生命周期之后保持对它的引用也是可能的,但这可能更难错过或意外发生。

    并不是说这是反对你正在做的事情的论据。事实上,我以前使用过这种模式(出于不同的原因),我认为它没有任何本质上的错误,除了你似乎认识到的缺乏封装。上述问题只是需要注意的事情。

    【讨论】:

      【解决方案3】:

      一个问题是您的类不再是可复制或可分配的,因此不能像向量一样存储在 C++ 容器中。另一个是维护您的代码的经验丰富的 C++ 程序员会看到它并惊呼“WTF !!”非常响亮,这绝不是一件好事。

      【讨论】:

      • 触摸。但我可以通过定义它们来解决这个问题(为简洁起见,我省略了)。
      • (我喜欢你的编辑使我的评论模棱两可——我现在可以指“定义复制构造函数”或“定义有经验的 C++ 程序员”。)
      • @mskfisher 这就是 SO 的奇妙之处 - 您评论的事实不应阻止我改进答案。任何感到困惑的人都可以随时查看编辑历史记录。我必须说我发现你的评论一开始就模棱两可——在我原来的答案中,“他们”是什么?
      • 定义拷贝构造函数和赋值运算符。
      • 100% 同意。调用内联访问器函数,而不是引用。
      【解决方案4】:

      你也可以做这样的事情,它适用于内置类型: (对不起,如果这段代码 sn-p 包含错误,但你明白了)

      template <typename T, typename F>
      class read_only{
         typedef read_only<T, F> my_type;
         friend F;
      
      public:
         operator T() const {return mVal;}
      
      private:
         my_type operator=(const T& val) {mVal = val; return *this;}
         T mVal;
      };
      
      
      class MyClass {
      public:
         read_only <int, MyClass> mInt;
         void MyFunc() {
            mInt = 7; //Works
         }
      };
      
      AnyFunction(){
         MyClass myClass;
         int x = myClass.mVal; // Works (okay it hasnt been initalized yet so you might get a warning =)
         myClass.mVal = 7; // Error
      }
      

      【讨论】:

      • 我不得不说这很聪明。
      • 我也喜欢你避开了按值返回的 const 引用,这消除了 Tyler 对我的方法的抱怨。
      • AFAIR 模板参数不能是friend,所以它不起作用,即使它起作用也应该是“friend class F”。有一些技巧可以解决这个问题,因为它们很讨厌,而且在任何地方都不起作用。
      • @Tomek:我在 VS2008 中制作并编译了一个类似的类(现在这里没有编译器),没有使用任何技巧。
      • 这并不意味着它是正确的;)。尝试使用在线 Comeau 编译器(不要选择 c++0x 扩展),它会失败。即使启用了 c++0x 扩展,g++ 4.4 也会失败。我看过关于该主题的非常有趣的文章(或者可能在书中的某处),并且它比较了很多编译器关于这个主题 - 它是一团糟......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-07-21
      • 2012-10-07
      • 1970-01-01
      • 2011-08-16
      • 2018-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多