문제

I have a class that the user uses to interface with a system. This class uses Pimpl to hide its internals, so its only actual member is a reference to the real, hidden object that does all the work.

Because the class has reference semantics, it's usually passed around by value much like a pointer. That leads to a problem with const correctness. You can break the const nature of the class very easily by simply copying a const value into a non-const value. And there's no way to avoid that than to prevent copying altogether.

I want to be able to return const values of these, which preserves the const nature of the object. Without creating a new class or something.

Basically I want to prevent this from working:

struct Ref
{
    int &t;
    Ref(int &_t) : t(_t) {}
};

Ref MakeRef(int &t) { return Ref(t); }

int main()
{
    int foo = 5;
    const Ref r(foo);
    const Ref c(r);            //This should be allowed.
    Ref other = MakeRef(foo);  //This should also be allowed.
    Ref bar(r);                //This should fail to compile somehow.

    return 0;
}

After all, it would fail to work if I did it directly:

int &MakeRef(int &t) {return t;}

int main()
{
    int foo = 5;
    const int &r(foo);
    const int &c(r);            //This compiles.
    int &other = MakeRef(foo);  //This compiles.
    int &bar(r);                //This fails to compile.

    return 0;
}
도움이 되었습니까?

해결책 2

What you are asking is not possible. It's not possible for these 2 lines to behave differently:

const Ref c(r);            //This should be allowed.
Ref bar(r);                //This should fail to compile somehow.

Both lines will execute through the same code path, they will both execute through the same copy constructor (yours or automatically generated). The only difference is the former will result in a const final variable.

The unfortunately reality is that even if you managed to prevent the above from compiling in your desired case, someone could simply do the following to circumvent your protection:

const Ref c(r);
Ref &bar = (Ref&)c;

If you're trying to prevent other people from doing nasty things to your class, you'll need to find an alternative to using a reference to a local variable. If you're just worried about yourself, then just don't do anything you shouldn't :)

다른 팁

This is exactly the same issue that comes from conflating const T* and T* const: the mutability of the reference and the referent are distinct. There are valid use cases in C++ for all four possible combinations. I would make distinct types for "reference to T" and "reference to const T":

#include <type_traits>

template <typename T>
struct Ref
{
    T &t;
    Ref(T &_t) : t(_t) {}
    Ref(const Ref<typename std::remove_cv<T>::type>& other) : t(other.t) {}
};

template <typename T>
Ref<T> MakeRef(T& t) { return {t}; }

template <typename T>
Ref<const T> MakeConstRef(const T& t) { return {t}; }

int main()
{
    int foo = 5;
    auto r = MakeConstRef(foo);
    auto c = r;                 // This is allowed.
    auto other = MakeRef(foo);  // This is also allowed.
    Ref<const int> baz = other; // This works, too.
    Ref<int> bar = c;           // This fails to compile, as desired.
}

Live example at ideone.

CV-modifers in general, do not move past a reference or pointer from within an class/struct definition. This is because it is not an aggregate part of the object, so technically, you are not really acting upon the object, just something that it is pointing at.

What you will have to do is roll your own constness like this:

struct constRef
{
    int const& _t;
    constRef(int const& rxo) : _t(rxo) {}
    constRef(constRef const& rxo) : _t(rxo._t) {}

    int const & t() const { return _t; }
    static constRef Make(int const & t) { return t; }
};

struct Ref
{
    int& _t;
    Ref(int& ro) : _t(ro) {}
    Ref(Ref const& rxo) : _t(rxo._t) {}
    operator constRef() const { return constRef(_t); }

    int& t() { return _t; }
    static Ref Make(int& t) { return t; }
};


int main()
{
    int foo = 5;
    Ref foo2(foo);               
    constRef r(foo2);            // non-const -> const         This compiles.
    constRef c(r);               // const -> const             This compiles.
    Ref other = Ref::Make(foo);  // non-const -> non-const     This compiles
    Ref bar(r);                  // const -> non-const         This fails to compile

    return 0;
}

This allows for automatic one way conversion between non-const to const type through the Ref::operator constRef() const conversion function. A working model can be found here.

The only problem with this is that any type of operations that are correct for const objects would have to the signatures duplicated and the bodies point at the const class's implementation.

A possible way of getting around this would be inheritance, but doing so will probably get even more messy and problematic, not to mention will reduce the compilers ability to optimise.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top