【问题标题】:Name for a public const and private writable attribute?公共常量和私有可写属性的名称?
【发布时间】:2017-12-15 16:44:43
【问题描述】:

在 C++ 编程中,我经常希望给类的用户对属性的只读访问权限,以及类本身的读写访问权限。我讨厌XxxGet() 方法,所以我经常将public const & 用于私有属性,如下所示:

class counter {
  private:
     int _count;

  public:
     const int & count;

     counter : _count( 0 ), count( _count ){}

     void inc( void ){ _counter++; }
};

这个技巧有通用名称吗?

【问题讨论】:

  • 访问器(getter/setter)不仅是限制对某些成员变量的访问的手段,它们还隐藏了实现。您的“技巧”泄漏了实现,这意味着您将永远无法更改 count 在您的班级中的表示方式。如果有一天你决定count 会更好地即时计算,或者访问计数应该同步以适应多线程,该怎么办?您必须修改counter 的所有用户。您可能应该坚持使用广为接受的习语,并使用 getter。
  • @Seb:可能不会,这带来的痛苦多于荣耀(即,它很容易在应用程序中产生错误,而与普通访问器相比几乎没有任何价值)
  • @Wouter:我想知道你为什么“讨厌”XxxGet() 方法?我的意思是,我也讨厌它们,但我会在这里使用它们,因为这正是你所需要的。
  • @JD:不知道,我想这对我来说感觉不对。我不喜欢 () 用于(至少在概念上)除了产生值之外什么都不做的东西。就像每次我需要那个常量时都要写两次 pi()。
  • 刚刚我想到了一个可以认为是同一种模式的名字。虽然通常不用于成员变量。

标签: c++


【解决方案1】:

我对那个把戏的个人名字是坏主意

我会避免您采用的方法,因为它会产生额外的不必要的成本。如果您添加访问器,它们可以根据需要内联,唯一的惩罚是必须输入额外的一对括号:

class counter {
    int _count;
public:
    counter() : _count() {}
    int count() const { return _count; }
    void inc() { ++_count; }
};

主要区别在于,在您的解决方案中,您将对象的大小增加一个引用(对于大多数实现,这意味着指针),然后每次访问都需要额外的间接。另一方面,通过访问器,使用实际变量,函数将被优化掉(内联,并解析为对变量的单次读取)。

作为该类型构造的专有名称,我从未在 C++ 中看到过您的特定构造,但如果您考虑其他语言,那是 C# 中 property 的基本概念,您可以在其中将 getter 设为公开,将 setter 设为私有。

编辑:我想坏主意可能会被误解为只是个人意见(确实如此),但请考虑该设计的副作用:

由于对象中的引用,您禁止了赋值运算符的隐式定义。更糟糕的是,复制构造函数编译但不能按预期工作:

// consider the implementation with the const reference
counter c1;
counter c2( c1 );          // compiles, so it must work
c2.inc();
std::cout << c2.count;   // outputs 0
// c2 = c1;              // error: well, at least this does not compile!

问题在于编译器生成的复制构造函数会使c2 中的count 引用与c1 中的count 引用所引用的int 引用相同,这可能导致难以- 发现代码中实际上很难调试的细微问题。

【讨论】:

  • 感谢您准确说明我关于复制构造函数的观点。然后+2 :)
  • 手工赋值运算符只会分配 _count 成员并保持引用不变。但是,复制构造函数是一个更严重的问题。
  • @UncleBens:对...我对必须重新分配引用感到困惑,但您说得对,引用不需要重新安装,因为它仍然引用本地成员。我已从答案中删除了该评论。复制构造函数实现起来也很简单,但必须手动实现,问题是它增加了出错的机会。
  • 你从未初始化_count。
  • @Mooing Duck:对不起? counter() : _count() {}?
【解决方案2】:

编辑

刚刚我想到了一个可以认为是相同模式的名字。虽然通常不用于成员变量。

实际上可能有一个名称,正如 Boost Tuple 库以及 TR1/C++11 实现所流行的那样:

打结

典型示例:

 tuple<int> tie(ref(some_var));
 // or shorter:
 auto tied = tie(var1, var2, var3);

作业并发症

我之前_立即想到的这个(反?)模式最接近的名称是:指针或引用别名。这不是一个很好的主意,原因很多,其中一些已经提到了

  • 类布局+大小
  • 复制/赋值语义
  • 编译器优化:当编译器知道引用可能指向相同的内存位置时,编译器将避免对(寄存器分配的)变量的值做出假设。

除了大卫提出的观点之外,编译器将无法生成默认值

  • 语义上有效复制构造函数
  • 赋值运算符
  • 移动赋值运算符

现在包含引用的类。另请注意,您的班级不可能再成为 POD

【讨论】:

  • @David:同意,我也希望要求使用T(T&amp;&amp;) = default; 来显式调用默认移动构造函数...
【解决方案3】:

其他一些人已经谴责了这个想法,我(大部分)倾向于同意他们的观点。虽然很多人可能(至少)同样不喜欢它,但如果我要支持这个订单上的某些东西,我会做这样的事情:

class counter { 
    int count_;
public:
    counter(int init=0) : count_(init) {}
    operator int() const { return count_; }
    void inc() { ++count_; }
};

其中一个问题通常与隐式转换共享:即使您不希望隐式转换也可能发生。 OTOH,它是用户提供的转换这一事实实际上消除了许多问题——在任何给定情况下,只有一个隐式转换会自动发生,因此(例如)您已向int 提供转换这一事实将意味着值为 0 的 counter 可以从 counter 隐式转换为 int 到 (null) pointer to T,因为这将涉及两个隐式转换。

有时这可能会导致问题,在这种情况下(从 C++11 开始)您可以将转换运算符设为 explicit,因此它只会在/如果用户进行显式转换时发生,例如:

counter t;

int x = t;  // allowed by code above, but not with `explicit` conversion operator.

int y = static_cast<int>(t);    // allowed with `explicit` conversion operator.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-27
    • 1970-01-01
    • 2018-05-09
    • 1970-01-01
    • 2012-04-18
    • 2012-02-23
    相关资源
    最近更新 更多