在 C++17 中不可变 class 中分配给 public const 变量的防御检查值的方法?

Way to defensive check value assigned to public const variable in immutable class in C++17?

在 Java 中断后回到 C++。尝试创建一个不可变对象并在 Java 中工作后,public const 变量似乎是最明智的(如 Java final)。

public:
     const int A;

一切都很好,但如果我想防御性地检查这个值,我该怎么做。下面的代码对我来说似乎很奇怪,但与 Java 最终成员不同,我似乎无法在防御性检查(编译器错误)后在构造函数中设置 A 。

MyObj::MyObj(int a) : A(a) {
        if (a < 0)
            throw invalid_argument("must be positive");
    }

A 的 public const 变量似乎比 getter 更清晰、更清晰的解决方案,仅在其后面有一个非 const int,但如果这是不好的做法,请接受该变量或其他想法.

你的例子应该可以正常工作:

class MyObj {
public:
  const int var;
  MyObj(int var) : var(var) {
    if (var < 0)
       throw std::invalid_argument("must be positive");
  }
};

(Live example, or with out-of-line constructor)

如果您希望 MyObj 永远不可变,那么 const 成员是 可能很好。如果你希望变量通常是不可变的,但仍然有可能用赋值覆盖整个对象,那么最好有一个 private 变量和 getter:

class MyObj {
  int var;
public:
  MyObj(int var) : var(var) {
    if (var < 0)
      throw std::invalid_argument("must be positive");
  }

  int getVar() const { return var; }
};

// now allows
MyObj a(5);
MyObj b(10);
a = b;

编辑

显然,你想要做的是

  MyObj(int var) {
    if (var < 0)
       throw std::invalid_argument("must be positive");
    this->var = var;
  }

这是不可能的;一旦 const 变量有一个值,它就不能改变。一旦构造函数的主体({} 位)开始,const 变量就已经有了一个值,尽管在这种情况下该值是 "undefined",因为您没有设置它(并且编译器因此抛出错误)。

而且,这个其实也没什么意义。在检查之后或检查之前设置变量没有效率差异,并且任何外部观察者都无法看到差异,因为 throw 语句将展开堆栈,立即解构对象。

通常 是常规做法 - 但您也可以考虑:

  1. 创建特定于域的类型并使用它们代替一般基元。例如,如果您的字段是电话号码,则有一个类型 TelephoneNumber ,在其构造函数(或工厂)中,采用一个字符串,执行您想要的所有电话号码验证(并抛出无效)。然后你写这样的东西:

    class Contact {
      const TelephoneNumber phone_;
     public:
      Contact(string phone) : phone_(phone) { ... }
      ...
    

    当您执行此操作时,TelephoneNumber 的构造函数将在初始化字段 phone_ 时调用带有字符串参数的构造函数并进行验证。

    以这种方式使用域特定类型在网络上以 "primitive obsession" 的名称作为 "code smell" 讨论。

    (IMO 这种方法的问题是你几乎必须在任何地方使用它,从你的项目开始,否则你开始不得不在整个地方和你的代码中进行显式(或隐式)转换看起来很糟糕,你永远无法确定你的值是否已经过验证。如果你正在使用现有的代码库,几乎不可能完全改造它,尽管你可能只是开始使用它,特别是 important/ubiquitous 类型。)

  2. 创建验证方法,该方法接受 return 一些值,并执行必要的验证 - 无效时抛出,否则 return 其参数。这是一个示例验证器:

    string ValidatePhoneNumber(string v) {
      <some kind of validation throwing on invalid...>
      return v;
    }
    

    并按如下方式使用:

    class Contact {
      const string phone_;
     public:
      Contact(string phone) : phone_(ValidatePhoneNumber(phone)) { ... }
    

    当应用程序或库对域特定类型进行如此多的验证时,我已经看到它被使用,以至于已经建立了这些域特定验证器方法的小型库并且代码阅读器已经习惯了它们。我不会真的认为它是惯用的,但它确实有一个优点,即验证就在你可以看到的地方。