2

I have a class which contains references, like:

class A {
 A(B &b) : b(b) {} // constructor
 B &b;
}

Sometimes b must be read-only, sometimes it is writeable. When I make a const A a(b); object, it's obvious that I want to protect the data inside it as const. But - by accident - it's easy to make a non-const copy of the object which will make the data inside it vulnerable.

const A a(b); // b object protected here
A a_non_const(a);
a_non_const.b.non_const_function(...); // b not protected now

I think that I should somehow prevent copies of the object when it is const like this:

const A a(b);
const A a2(a); // OK!
A a_non_const(a); // Compiler error

Is this possible at all?

Bill Kotsias
  • 3,258
  • 6
  • 33
  • 60

2 Answers2

2

You can do it for the heap:

static const A *constCopy(const A &a); // and of course implement it somewhere

Then you will not accidentally modify the object via the pointer you get (which has to be stored in const A *, otherwise the compiler will complain).

However it will not work with stack-based objects, as returning const A & of a local variable is a rather deadly action, and "const constructor" has not been invented yet (related: Why does C++ not have a const constructor?)

tevemadar
  • 12,389
  • 3
  • 21
  • 49
2

flaw in your code: your data isn't "protected" even with const

The const type qualifier manages access to the member functions of a type as well as the access to its members. Since your member B & b is a reference, const doesn't do much for you here: A reference cannot be changed after initialization either way. How you access the target of that reference isn't even considered:

const A a(b);
a.b.non_const_function(); // OOPS, no problem!

solution with templates

Instead of (ab)using the const type qualifier you could add a "flag" to your type, to differentiate between cases where you need to be able to have non-const access and case where you don't:

#include <type_traits>

struct B {
    void danger() {
    }
    void all_fine() const {
    }
};

template<bool Writeable>
struct A {
    using BRef = typename std::conditional<Writeable, B &, B const &>::type;
    BRef b;

    A (BRef b) : b(b) {};
};

using ConstA = A<false>;
using NonConstA = A<true>;

int main() {
    B b;
    ConstA a(b);
    //NonConstA nc_a(a);
    ConstA another_a(a);
    //another_a.b.danger();
    another_a.b.all_fine();

    NonConstA a2(b);
    a2.b.danger();
}

With some std::enable_if you can then selectively enable / disable member functions of A depending on whether they need "writeable" b or not.

real solution: refactor your design

BUT I'd like to highlight this comment even more:

"Sometimes b must be read-only, sometimes it is writeable." All your problems stem from this weird duality. I suggest picking one set of semantics for your class, not two

From Lightness Races in Orbit

You should probably instead consider splitting your class such that you have a CommonA with functionality used by both a WriteableA and a NonWriteableA (the names are terrible, but I hope you understand what I mean).

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • You are right, I didn't give the full story. I have this "design" where you return the b as const or non-const, like this: `B& Get_b() { return b };` `const B& Get_b() const { return b; }`, which I am not very fond of anyway! – Bill Kotsias Mar 22 '18 at 15:10