1

In the library I am designing, I sometimes need read-access to large member variables of classes. Because of their size I don't want to make a getter that returns by copying the member. I don't want them to be modifiable from the outside, so I can't have them public or return references to them. So I thought I would use a "reader":

class TestClass
{
public:
    explicit TestClass(double d): d_(d){}

    const double& readD() const { return d_; }

private:
    double d_;
};

(this is not really meant for doubles)

But here somebody could const_cast the reference and access the data directly. Even without assuming malicious intent, somebody could safe a reference to the data-member and keep it around after the original object has gone out of scope. I know const references can keep a temporary viable, but that doesn't remove the const_cast-problem. So I came up with a workaround:

#include <iostream>

template<class T>
class SafeMemberReference
{
public:
    using type = T;
    SafeMemberReference(const T& t) :t(t) {}
    explicit SafeMemberReference(T&& t) = delete;

    operator const T& () && {return t; }
    T get() && {return t; }
private:
    const T& t;
};

class TestClass
{
public:
    explicit TestClass(double d): d_(d){}

    SafeMemberReference<double> readD() const { return d_; }

private:
    double d_;
};

int main()
{
    TestClass foo(1.2);

    // temporary from read can be used as temporary in expressions
    std::cout << foo.readD() << std::endl;

    // temporary can be used to copy from
    auto x = foo.readD().get();

    // lvalue can not be used, so a possible dangling reference is no problem
    auto ref = foo.readD();
    //std::cout << ref << std::endl;
 }

I have several questions to this:

Q1) How necessary is this from an efficiency POV? the largest objects I am returning are dense complex matrices with dimensions of maybe 1000x1000. These copies may happen frequently

Q2) Are my concerns about returning by const& valid?

Q3) Does this seem like a good solution? which drawbacks does it have?

tillh
  • 185
  • 2
  • 9
  • 1
    What problem does `SafeMemberReference` solve? It neither solved the problem with dangling references nor avoiding unnecessary copies nor preventing modifications using `const_cast`. May as well just return a copy or a `const &` directly. – nwp Mar 16 '17 at 14:22
  • You have data to work with. You have a class that encapsulates it. Why not to implement all needed data operations as this class methods?? With no direct access to data. – KonstantinL Mar 16 '17 at 14:56

1 Answers1

4

Any solution that attempts to fight the language itself is not a good solution.

They should get a wrap on the knuckles if they used a const_cast in that way: the behaviour on trying to change an object via a const_cast on an object that was originally declared as const is undefined. Even if you manage to conjure up a solution to prevent that, a hostile programmer could still take the address of your object, offset that address (using unsigned char* pointer arithmetic) and modify a data member through that pointer!

So if I were you I wouldn't fight the language. Return a const reference if I were you, as per your original suggestion.

Code static analysis tools / compiler warnings / code reviews / human resource departments will help you keep other collaborative programmers on the straight and narrow.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • For more info on when `const_cast` is OK you may want to read the [cpp-reference](http://en.cppreference.com/w/cpp/language/const_cast), a question ["When should I use ...?"](http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used), and a question ["Is const_cast safe?"](http://stackoverflow.com/questions/357600/is-const-cast-safe). – TobiMcNamobi Mar 16 '17 at 14:38
  • Out of curiosity: is this really undefined behavior in OP's case? [Is const_cast safe?](http://stackoverflow.com/q/357600/1683161) says a `const_cast` is safe if the original object is non-const (which OP's `d_` is). – rainer Mar 16 '17 at 14:52