Krzysztof Stanisławek
Krzysztof Stanisławek

Reputation: 1277

How can I keep const-correctness and RAII?

I have situation similar to included:

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

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

I can't have shared_ptr to B before construction, so before a is initialized. So, I need to initialize my constant field after construction (I think it denies RAII), or just construct it later (so it can't be const, so it denies const-correctness, and also looks like not-too-consistent with RAII).

It looks like propably common situation. Is there any the cleanest way to handle this? How would you do this?

Upvotes: 2

Views: 328

Answers (3)

Jonathan Wakely
Jonathan Wakely

Reputation: 171303

If A doesn't actually keep a copy of the shared_ptr<B> you pass it (just uses the B briefly) then you can make this work (see below) but:

  1. If A doesn't keep a reference to the B then it could just take a B& or B* argument, not a shared_ptr<B>, so you should change the design.
  2. If A does keep a reference then you're going to have a circular reference, so you should change the design.

This works, but is really, really horrible and would be easy to introduce bugs, and is generally a bad idea, I probably shouldn't even be showing it, just change your design to avoid circular dependencies:

#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);
}

Upvotes: 0

example
example

Reputation: 3419

Such a situation is a good indicator to refactor your code. Think about whether B should actually inhert from A or be a member of A before finding a way around this problem...

.. because it is probably going to be to remove the constness of your object - and probably not use shared_ptr (you have a cyclical reference there, so ref-counting alone will never be able to destroy your objects!).

Upvotes: 2

I would solve this by not having const members, plain and simple. They are generally much more trouble than they're worth (they make the class non-assignable, not even move-assignable, for example).

a is private, so only the class itself can access it. Thus it should be enough to document "a should never be modified after being initialised!!!". If you fear that won't be enough (or the class has friends outside your control), you can make this even more obvious like this:

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;
};

Upvotes: 2

Related Questions