Search code examples
c++constantspass-by-referencecopy-constructordefault-copy-constructor

Make only const copies of a const object


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?


Solution

  • 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).