0

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

因为类具有引用语义,所以它通常像指针一样按值传递。这会导致const正确性问题。您可以通过简单地将一个值复制到一个非值中来非常容易地打破const类的性质。没有办法避免这种情况,而不是完全防止复制。constconst

我希望能够返回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;
}
4

3 回答 3

2

const T*这与将and混为一谈的问题完全相同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.
}

ideone 上的活生生的例子

于 2013-06-13T22:26:03.977 回答
0

你问的是不可能的。这两条线的行为不可能不同:

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

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

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

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

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

于 2013-06-13T22:12:28.940 回答
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;
}

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

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

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

于 2013-06-14T01:25:07.773 回答