具有引用语义的 const 对象

Posted

技术标签:

【中文标题】具有引用语义的 const 对象【英文标题】:Const objects with reference semantics 【发布时间】:2013-06-13 21:47:32 【问题描述】:

我有一个用户用来与系统交互的类。此类使用 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;

【问题讨论】:

您可以将复制构造函数设为私有。 @JonathanPotter:那么没有人可以以任何理由复制它。 如何删除复制构造函数并创建一个接受非常量引用的构造函数? @syam:您将无法复制临时对象并且无法将const 对象构造到另一个对象。因此,我永远无法按值返回 const T 啊,对,我不知何故跳过了你说“保持不变性”的部分。据我所知,类的层次结构是解决方案(基础ConstRef + 派生Ref 和适当的访问器)但不幸的是,这并不能回答你的问题,因为你明确不希望那样。 【参考方案1】:

这与将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.

【讨论】:

这是一个 Pimpl 类;它不会将其私有信息作为某种模板参数公开。 @NicolBolas:重点在于,对于引用到 const 和引用到可变的不同类型,这最容易实现,引用到可变可以隐式转换为引用到常量.我用模板轻松实现了这两种类型,您可能需要两种显式类型。 Ref&lt;int&gt; &amp;bar = (Ref&lt;int&gt;&amp;)c; ? @MattHouser supposed 失败 - 这是设计要求。 @MattHouser: Ref&lt;int&gt; &amp;bar = (Ref&lt;int&gt;&amp;)c; 这会产生未定义的行为。 Ref&lt;int&gt;Ref&lt;const int&gt; 彼此没有任何关系。您也可以将int&amp; 转换为float&amp;【参考方案2】:

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

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;

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

【讨论】:

糟糕。我在上面更新了我的代码。 Ref &amp;bar = (Ref&amp;)c; 只会将一个 const 对象转换为该 const 对象的非常量引用。没有预防措施。 这就像说const-正确性不存在,因为有人总是可以const-围绕它进行转换(或者在你的情况下,C-style cast)。这是故意滥用,而不是 int &amp;bar(r) 防止的意外滥用。 这是我的观点之一。为什么 OP 试图阻止这种情况?只是他想自救,还是想阻止一个不择手段的人绕过他的安全?从自己手中拯救自己需要付出很多努力。 "为什么 OP 试图阻止这种情况发生?" 你可能会问为什么我们有 const-correctness 根本。。跨度> 【参考方案3】:

一般来说,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 转换函数在非常量类型和常量类型之间进行自动单向转换。可以找到工作模型here。

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

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

【讨论】:

以上是关于具有引用语义的 const 对象的主要内容,如果未能解决你的问题,请参考以下文章

可变引用是不是具有移动语义?

const 引用函数参数:是不是可以禁止临时对象?

C++11 ——— 右值引用和移动语义

C++ 中的值语义技巧

从 const 引用初始化非常量对象时防止复制

具有共享所有者语义的容器