C++ RAII:在const方法中初始化数据成员

C++ RAII:在const方法中初始化数据成员,c++,lazy-evaluation,const-correctness,C++,Lazy Evaluation,Const Correctness,在RAII中,资源在被访问之前不会初始化。然而,许多访问方法被声明为常量。我需要调用mutable(非常量)函数来初始化数据成员 示例:从数据库加载 struct MyClass { int get_value(void) const; private: void load_from_database(void); // Loads the data member from database. int m_value; }; int MyClass :: ge

在RAII中,资源在被访问之前不会初始化。然而,许多访问方法被声明为常量。我需要调用
mutable
(非常量)函数来初始化数据成员

示例:从数据库加载

struct MyClass
{
  int get_value(void) const;

  private:
     void  load_from_database(void); // Loads the data member from database.

     int m_value;
};

int
MyClass ::
get_value(void) const
{
  static bool value_initialized(false);
  if (!value_initialized)
  {
    // The compiler complains about this call because
    // the method is non-const and called from a const
    // method.
    load_from_database();
  }
  return m_value;
}
我的基本解决方案是将数据成员声明为
mutable
。我宁愿不这样做,因为这表明其他方法可以改变成员


如何转换
load\u from_database()
语句以消除编译器错误?

这不是RAII。在RAII中,您将在构造函数中初始化它,这将解决您的问题

因此,您在这里使用的是
Lazy
。不管是延迟初始化还是延迟计算

如果你不使用
mutable
,你会受到伤害

当然,您可以使用
常量演员
,但如果有人这样做了怎么办:

static const MyClass Examplar;
编译器认为它是只读内存的一个很好的候选者?在这种情况下,
const\u cast
的效果是未定义的。充其量,什么也没发生

如果您仍然希望继续执行
const_cast
路线,请按照
R Samuel Klatchko
的方式执行


如果您仔细考虑并认为可能有更好的替代方案,您可以决定包装您的变量。如果它是在自己的类中,只有3种方法:
get
set
load\u from\u database
,那么您就不会担心它是
可变的

您基本上是在实现缓存机制。我个人认为,将缓存数据标记为可变数据是可以的。

不要在这里使用const\u cast,否则会自找麻烦。在这种情况下使用mutable应该不会有问题,但是如果探查器没有提出其他建议,那么我认为用户看到构建成本高昂的对象时,会比第一次调用成本高昂的访问器方法更不惊讶。

如果您的方法更改了对象的状态(例如,通过更改基础数据库的状态),则该方法不应为常量。在这种情况下,您应该有一个单独的非常量
load
-方法,必须在调用
const
getter之前调用该方法

此方法既不需要
const_cast
not
mutable
,也不需要显式执行可能代价高昂的操作。

[LOOK MA!NO cast!:)]


我们的想法是使用政治上正确的
MyClass
,其中
const
方法不进行任何变异,而是通过const指针调用聚合对象的
const
和非
const
方法

正如Matthieu已经指出的,你在这里试图做的事与RAII几乎没有关系。同样,我怀疑
const
mutable
的任何组合是否真的有帮助
const
mutable
修改类型,并平等地应用于对该类型对象的所有访问

您似乎希望少量代码具有写访问权限,而其他任何代码都只具有对值的读访问权限。给定C++的基本设计(和最相似的语言),正确的方法是将变量移到它自己的类中,需要少量的代码作为类的一部分(或者可能是该类的朋友)。通过类的接口(即,检索值的成员函数)为世界的其他部分提供只读访问权限

您发布的(大概是精简的)
MyClass
非常接近右边——您只需要单独使用它,而不是作为包含大量其他成员的更大类的一部分。要更改的主要内容是1)名称从
MyClass
改为类似
lazy\u int
,以及2)(至少根据我的偏好)
get\u value()
可能应该重命名为
operator int()
。是的,
m_value
可能需要是可变的,但这不允许其他代码写入该值,因为其他代码根本无法访问该值本身


然后将该类型的对象嵌入到更大的类中。由于它的
操作符int()
,该外部类中的代码可以将其视为int(只读),但不能编写它,原因很简单,因为该类没有提供任何方式来编写它。

请确保通知用户,第一次调用getXXX函数可能需要一些时间。。。或者用户可能会在性能非常重要的关键部分第一次调用该函数…@Thomas Matthews,这里有一个链接解释RAII实际上是什么“在RAII中,资源在被访问之前不会初始化。”RAII代表“资源获取就是初始化”,因此,您的第一个语句是false不仅让人想起“最麻烦的解析”,而且在MyClass的所有实例之间共享。Raii是否真的“使用C++析构函数语义来管理资源”(UCDSTMR),参见……并且对于反对康斯坦斯卡斯特,这几乎总是错误的,它似乎只是可变成员(成员)存在的理由。这基本上是Jerry Coffin所陈述的,但您提供了示例代码。@Thomas Matthews实际上我(部分)受到Matthieu M.文章最后一部分的启发。本期主题是使用
惰性初始化
初始化变量,这是第一次访问该变量。将来对此变量的所有访问都是只读的。数据库不会更改,只更改初始化顺序。
struct DBValue 
{
  int get_value();

private:
  void load_from_database();
  int value;
};

struct MyClass 
{
  MyClass(): db_value(new DBValue()) {}
  ~MyClass() { delete db_value; } 

  int get_value() const;

private:
  DBValue * const db_value;
};

int MyClass::get_value() const
{
  return db_value->get_value(); // calls void load_from_database() if needed
}