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

Posted

技术标签:

【中文标题】RAII:在 const 方法中初始化数据成员【英文标题】:RAII: Initializing data member in const method 【发布时间】:2010-03-19 16:31:54 【问题描述】:

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_from_database() 语句以消除编译器错误?

【问题讨论】:

确保通知用户第一次调用 getXXX 函数可能需要一些时间......或者用户可能会在性能很重要的关键部分第一次调用该函数...... @Thomas Matthews,这是一个解释 RAII 实际是什么的链接en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization "在 RAII 中,资源在被访问之前不会被初始化。" RAII 代表“Resource acquisition Is Initialization”,所以你的第一个陈述是错误的。 static bool value_initialized(false); 不仅让人想起“最令人头疼的解析”,而且在 MyClass 的所有实例之间共享​​>。这是需要的吗? RAII 真的是“使用 C++ 析构语义来管理资源”(UCDSTMR),见***.com/questions/712639/… 【参考方案1】:

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

所以,你在这里使用的是Lazy。无论是惰性初始化还是惰性计算。

如果你不使用mutable,你将进入一个受伤的世界。

当然你可以使用const_cast,但是如果有人这样做了怎么办:

static const MyClass Examplar;

编译器认为它是只读内存的良好候选者?好吧,在这种情况下,const_cast 的效果是未定义的。充其量,什么都不会发生。

如果你还想继续const_cast 路线,按照R Samuel Klatchko 做。

如果您仔细考虑并认为可能有更好的选择,您可以决定包装您的变量。如果它属于自己的类,只有 3 个方法:getsetload_from_database,那么你就不用担心它是 mutable

【讨论】:

...反对 const_cast 这几乎总是错误的【参考方案2】:

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

【讨论】:

更多,这似乎只是 mutable(成员)存在的理由。【参考方案3】:

正如 Matthieu 已经指出的那样,您在这里尝试做的事情与 RAII 几乎没有关系(如果有的话)。同样,我怀疑constmutable 的任何组合是否真的会有所帮助。 constmutable 修改类型,并同样适用于所有对该类型对象的访问。

您似乎想要的是少量代码具有写入权限,而其他任何内容都只能读取该值。鉴于 C++(和大多数类似语言)的基本设计,正确的方法是将变量移动到它自己的类中,将需要写访问权限的少量代码作为一部分(或者可能是) 那堂课。世界其他地方通过类的接口(即检索值的成员函数)获得只读访问权限。

您发布的MyClass(可能已被剥离)非常接近正确——您只需要单独使用它,而不是作为具有许多其他成员的更大类的一部分。要更改的主要内容是 1) 将名称从 MyClass 更改为 lazy_int,以及 2)(至少按照我的偏好)get_value() 可能应该重命名为 operator int()。是的,m_value 可能需要是可变的,但这不允许其他代码写入该值,因为其他代码根本无法访问该值本身。

然后,您将该类型的对象嵌入到更大的类中。由于其operator int(),该外部类中的代码可以将其视为int(在只读的基础上),但不能编写它,仅仅是因为该类没有提供任何方法。

【讨论】:

【参考方案4】:

[看妈妈!没有演员表! :))]

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

我们的想法是拥有一个政治上正确的 MyClassconst 方法除了调用 const 和非 const 方法之外不会改变任何东西聚合对象通过 const 指针

【讨论】:

这基本上是 Jerry Coffin 所说的,但您提供了示例代码。 @Thomas Matthews 实际上,我(部分)受到了 Matthieu M. 帖子的最后一部分的启发。【参考方案5】:

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

【讨论】:

【参考方案6】:

如果你的方法改变了对象的状态(例如通过改变底层数据库的状态),那么方法不应该是 const。在这种情况下,您应该有一个单独的非 const load-方法,必须在调用 const getter 之前调用它。

这种方法既不需要const_cast,也不需要mutable,并且会使潜在的昂贵操作显式化。

【讨论】:

问题的主题是变量使用lazy initialization初始化,是第一次访问。以后对该变量的所有访问都是只读的。数据库没有变化,只是初始化的顺序。

以上是关于RAII:在 const 方法中初始化数据成员的主要内容,如果未能解决你的问题,请参考以下文章

C语言 结构体声明中const成员如何初始化

statuc和const

C++_const 常量成员以及成员初始化列表及其常量类

C++_const 常量成员以及成员初始化列表及其常量类

C++_const 常量成员以及成员初始化列表及其常量类

类的const成员