【问题标题】:Const reference field as readonly property in C++ classconst 引用字段作为 C++ 类中的只读属性
【发布时间】:2018-02-26 18:07:38
【问题描述】:

在 C++ 类中使用 const 引用字段作为只读 getter 好不好?

我的意思是,这段代码符合良好的做法吗?

class check{
private:
    int _x;
public:
    const int& x = _x;
    void setX(int v){
        _x = v;
    }
};

它的工作方式非常类似于 C# 属性、恕我直言,并且在类使用代码中非常简单明了:

  check s;
  int i;
  std::cin >> i;
  s.setX(i);
  std::cout << s.x << '\n';
  s.setX(7);
  // s.x = i; // Error
  std::cout<<s.x<<'\n';

【问题讨论】:

  • 并且可以被 const 强制转换为遗忘 :)
  • 不,这是一种不好的做法,因为与直接的内联 getter 函数相比,存储此引用可能会导致额外的内存开销。另一件事是,无论访问的值如何,您都无法执行运行时检查/断言。不幸的是,没有真正的方法可以模拟类似 C# 属性的语法。有一个MS-specific property extension,但它们是非标准的,被废弃了。
  • 尝试将一个check 分配给另一个。
  • IMO 不,这很烦人,并且与价值语义和生命周期有关。例如。如果您的对象被移动或复制,那么引用仍然是指旧对象的,可能会被销毁
  • 需要注意的是,一种语言中的习语不在另一种语言中,尝试使用 c++ 作为另一种语言不太可能正常工作

标签: c++ oop properties reference design-principles


【解决方案1】:

此代码符合良好做法吗?

并非如此,因为它引入了不必要的复杂性和空间开销。

此外,无论访问的值如何,您都无法执行运行时检查和/或断言。

此外,生命周期和语义会发生什么?

尝试将代码中的一个check 分配给另一个,看看会发生什么。作业格式不正确,因为该类是不可分配的。您应该提供一个复制和移动构造函数来处理引用,这样它就不会引用旧对象的数据成员。

最好直接使用_x,并有一个简单的内联getter函数。


PS:C#-like properties in native C++?

【讨论】:

  • Try assigning one check in your code to another and see what happens. Your object is copied 不,不是。作业格式不正确,因为课程不可分配。
  • 复制问题仅意味着您必须提供复制和移动构造函数以确保引用指向本地对象。赋值不是问题,因为无论如何都不可能更改赋值运算符中的引用,而且也不需要析构函数。
【解决方案2】:

一般来说,这不是一个好习惯。

恕我直言,在类使用代码中非常简单明了。

为什么要更清晰、更容易?

  • 您引入了另一个变量成员(无用的开销)。 (通常,引用将作为附加成员指针实现)。
  • 它使代码更难维护。您实际上是在变量成员之间创建依赖关系。
  • 它在分配和复制操作中产生问题。复制操作应该如何工作?

“经典”方法对我来说听起来更清晰,例如:

class Foo {
 public:
  void set_num(int value) noexcept { m_num = value; }
  int get_num() const noexcept { return m_num; }
  void set_string(std::string value) noexcept {
      m_str = std::move(value);
  }
  const std::string& get_string() const noexcept {
      return m_str;
  }
 private:
  int m_num;
  std::string m_str;
};

从性能的角度来看,应该首选这种方法。

  • 时序复杂性:在 inline 函数上调用 get_variable 不会引入比您的“参考方法”更多的开销。此外,编译器可对其进行高度优化(因为代码简单明了)。
  • 空间复杂度:它不会引入额外的数据成员。

【讨论】:

    【解决方案3】:

    您的建议通常是个坏主意:

    • 您无法通过执行任何处理来实现该属性(例如,您可以使用 getter 存储使用 [x,y] 的坐标,然后决定更改实现以使用 [angle,radius] 同时保持相同的公开界面)。
    • 使用 const 成员变量会产生空间开销,并且与内联 getter 相比不会给您带来任何性能优势。
    • 这不是惯用语。
    • 一旦你发布了你的类,并且其他人开始使用它,你就会被它困住:改变它来使用一个方法已经太迟了。

    如果您使用属性的目的是使代码更简洁,则不必在函数名称中使用“get”和“set”这两个词;这是一种老式的做法。可以使用“属性”的实际名称作为函数名,并且可以重载getter和setter:

    class Cheque {
    public:
        int x() const {
            return x_;
        }
        Cheque & x(int newX) {
            x_ = newX;
            return *this;
        }
    private:
        int x_;
    }
    
    // Usage:
    // int amt = myCheque.x(1234);
    // Cheque c = Cheque().x(123);
    

    在上面的代码中返回*this 使您可以使用method chaining;一种实现Fluent interface 成语的方法。

    【讨论】:

      【解决方案4】:

      当 C# 编译一个属性时,它会被编译成一个 getter 和一个 setter 函数。

      下面是一些证明这一事实的 C# 代码:

      using System;
      
      namespace Reflect
      {
          class Program
          {
              class X
              {
                  public int Prop { get; set; }
              }
      
              static void Main(string[] args)
              {
                  var type = typeof(X);
                  foreach (var method in type.GetMethods())
                  {
                      Console.WriteLine(method.Name);
                  }
                  Console.ReadKey();
              }
          }
      }
      

      你的输出应该是:

      get_Prop
      set_Prop
      ToString
      Equals
      GetHashCode
      GetType
      

      get_Prop 是实现 getter 的函数。 set_Prop是实现setter的函数。

      因此,即使您所做的事情看起来相似,也完全不一样。

      坦率地说,几乎所有在 C++ 中模拟“属性语法”的方法都会以这样或那样的方式失败。大多数解决方案要么会消耗您的内存,要么会存在一些限制,使其变得更加麻烦而不是有用。

      只要学会与 getter 和 setter 一起生活。 Getter 和 setter 是好习惯。 它们很短,很简单,很灵活,它们通常是内联的好人选,每个人都明白自己在做什么等等。

      【讨论】:

        猜你喜欢
        • 2013-05-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-23
        • 2011-04-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多