C++类中作为只读属性的const引用字段

16
在C++类中,将const引用字段用作只读获取器是一种好的实践吗?我的意思是,这段代码是否符合良好的实践?
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';

4
可以将其 const 转换为遗忘(oblivion) :) - StoryTeller - Unslander Monica
5
不,这是一种不好的做法,因为与直接使用内联 getter 函数相比,存储此引用可能会导致额外的内存开销。另一件事是,您将无法对所访问的值执行运行时检查/断言。不幸的是,没有真正的方法来模拟 C# 属性-类似的语法。虽然有一个 MS-specific property extension,但它们是非标准的并已被弃用。 - user7860670
11
尝试将一个“check”指派给另一个。 - molbdnilo
5
不,这很烦人并且会干扰值语义和生命周期。例如,如果你的对象被移动或复制,那么引用仍然指向旧对象,可能已经被销毁。 - M.M
5
需要注意的是,每种语言中的习语都不尽相同,试图将C++用作另一种语言可能会带来不良后果。 - Passer By
4个回答

19
这段代码符合良好实践吗?
并不完全符合,因为它引入了不必要的复杂性和空间开销。
此外,无论访问的值如何,您都将无法执行运行时检查或断言。
此外,生命周期和语义会发生什么?
尝试将代码中的一个"check"赋值给另一个变量,看看会发生什么。这个赋值是不正确的,因为该类不可赋值。您应该提供一个复制和移动构造函数来处理引用,以便它不会引用旧对象的数据成员。
最好直接使用"_x"并拥有一个简单的内联getter函数。
PS:C#-like properties in native C++?

尝试将代码中的一个检查项分配给另一个并观察发生了什么。你的对象被复制了吗?不,它没有。这个赋值是不合法的,因为该类是不可分配的。 - eerorika
复制问题只意味着您必须提供复制和移动构造函数,以确保引用指向本地对象。赋值不是问题,因为在赋值运算符中无法更改引用,因此也不需要析构函数。 - cmaster - reinstate monica

6
一般来说,这并不是一个好的实践。
在类中使用代码时,我认为经常是易懂干净的。
为什么这样更清晰、更容易呢?
因为:
1.引入其他变量成员(无用开销)。通常,引用会被实现为额外的成员指针。 2.它使得代码更难维护。实际上,你正在创建变量成员之间的依赖关系。 3.它在赋值和复制操作中产生问题。复制操作应该如何工作?
对我来说,“经典”方法更加清晰,比如:
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;
};

就性能而言,这种方法应该更受欢迎。

  • 时间复杂度:在内联函数中调用get_variable并不比您的“引用方法”增加更多的开销。此外,它很容易被编译器进行高度优化(由于代码的直接性)。
  • 空间复杂度:它不会引入额外的数据成员。

4
你提出的想法总的来说并不好:
  • 你不能通过任何处理方式实现属性(例如,通过getter可以使用[x,y]存储坐标,并在保持相同公共接口的情况下决定更改实现以使用[angle,radius])。
  • 使用const成员变量会产生空间开销,与内联获取器相比,不会给你带来任何性能优势。
  • 这不符合习惯用法。
  • 一旦你发布了你的类,并且其他人开始使用它,你就被困在里面了:改用方法来代替已经太迟了。

如果你的目的是为了使代码更简洁,请不必在函数名中使用“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使您能够使用方法链;这是实现流畅接口惯用语的一种方式。


1
当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 是良好的实践。 它们简短、简单、灵活,通常是内联的良好候选项,每个人都知道它们的作用等等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接