如何保持常量正确性和RAII

How can I keep const-correctness and RAII?

本文关键字:RAII 正确性 常量 何保持      更新时间:2023-10-16

我有类似于包含的情况:

class A
{
    public:
        A(shared_ptr<B>);
}
class B : public enable_shared_from_this<B>
{
    const shared_ptr<A> a;
}

在构造之前,我不能有shared_ptrB,所以在初始化a之前。因此,我需要在构造后初始化我的常量字段(我认为它否认RAII(,或者稍后再构造它(所以它不能是const,所以它否认const的正确性,而且看起来与RAII不太一致(。

这看起来很常见。有什么最干净的方法来处理这个问题吗?你会怎么做?

我会通过没有const成员来解决这个问题,简单明了。它们通常比它们的价值要麻烦得多(例如,它们使类不可赋值,甚至不可移动(。

a是私有的,所以只有类本身可以访问它。因此,它应该足以记录"a在初始化后永远不应该被修改!!"。如果你担心这还不够(或者这个类有你无法控制的friend(,你可以这样做:

class B : public enable_shared_from_this<B>
{
  const std::shared_ptr<A>& a() { return _use_this_ONLY_for_initialising_a; }
  std::shared_ptr<A> _use_this_ONLY_for_initialising_a;
};

这样的情况是重构代码的好指标。在找到解决这个问题的方法之前,想想B是否应该真正继承A或成为A的成员。。。

因为它可能会消除对象的常量,并且可能不使用sharedptr(你有一个循环引用,所以单靠引用计数永远无法破坏你的对象!(。

如果A实际上没有保存shared_ptr<B>的副本,您可以传递它(只是短暂地使用B(,那么您可以执行此操作(见下文(,但是:

  1. 如果A没有保留对B的引用,那么它可能只需要B&B*参数,而不是shared_ptr<B>,因此您应该更改设计
  2. 如果A确实保留了一个引用,那么你将有一个循环引用,所以你应该更改设计

这是有效的,但真的,真的很可怕,很容易引入错误,而且通常是个坏主意,我可能甚至不应该展示它,只是改变你的设计以避免循环依赖:

#include <memory>
#include <iostream>
class B;
class A
{
public:
  A(std::shared_ptr<B> b);
};

class B : public std::enable_shared_from_this<B>
{
  // return a shared_ptr<B> that owns `this` but with a
  // null deleter. This does not share ownership with
  // the result of shared_from_this(), but is usable
  // in the B::B constructor.
  std::shared_ptr<B> non_owning_shared_from_this()
  {
    struct null_deleter {
      void operator()(void*) const { }
    };
    return std::shared_ptr<B>(this, null_deleter());
  }
public:
  B(int id)
  : m_id(id), a(std::make_shared<A>(non_owning_shared_from_this()))
  { }
  int id() const { return m_id; }
private:
  int m_id;
  const std::shared_ptr<A> a;
};
A::A(std::shared_ptr<B> b)
{
  std::cout << b->id() << std::endl;
}
int main()
{
  auto b = std::make_shared<B>(42);
}