具有引用语义的Const对象

时间:2013-06-13 21:47:32

标签: c++ const-correctness

我有一个用户用来与系统连接的类。这个类使用Pimpl隐藏它的内部,因此它唯一的实际成员是对完成所有工作的真实隐藏对象的引用。

因为类具有引用语义,所以它通常通过值传递,就像指针一样。这会导致const正确性出现问题。只需将const值复制到非const值,即可轻松破解类的const性质。除了完全阻止复制之外,没有办法避免这种情况。

我希望能够返回这些值的const值,这会保留对象的const性质。 没有创建新类或其他东西。

基本上我想阻止这种情况发生:

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

毕竟,如果我直接这样做,它将无法工作:

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

3 个答案:

答案 0 :(得分:2)

这与将const T*T* const合并而来的问题完全相同:引用和引用的可变性是截然不同的。对于所有四种可能的组合,C ++中都有有效的用例。我会为“引用T”和“引用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

答案 1 :(得分:0)

你问的是不可能的。这两行不可能表现不同:

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

两行都将通过相同的代码路径执行,它们都将通过相同的复制构造函数(您的或自动生成)执行。唯一的区别是前者会产生一个const最终变量。

不幸的是,即使你设法阻止上述内容在你想要的情况下进行编译,有人也可以简单地执行以下操作来规避你的保护:

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

如果你试图阻止其他人为你的班级做些讨厌的事情,你需要找到一个替代方法来使用对局部变量的引用。如果你只是担心自己,那就不要做任何你不应该做的事情:)

答案 2 :(得分:0)

通常,CV修饰符不会从类/结构定义中移过引用或指针。这是因为它不是对象的聚合部分,所以从技术上讲,你并没有真正按照对象行事,只是指向它的东西。

你需要做的就是像这样滚动自己的常量:

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

这允许通过Ref::operator constRef() const转换函数在非const到const类型之间进行自动单向转换。可以找到工作模型here

唯一的问题是,对于const对象正确的任何类型的操作都必须重复签名,并且主体指向const类的实现。

解决这个问题的一种可能方法是继承,但这样做可能会变得更加混乱和有问题,更不用说会降低编译器优化的能力。