具有引用语义的 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<int> &bar = (Ref<int>&)c;
?
@MattHouser supposed 失败 - 这是设计要求。
@MattHouser: Ref<int> &bar = (Ref<int>&)c;
这会产生未定义的行为。 Ref<int>
和 Ref<const int>
彼此没有任何关系。您也可以将int&
转换为float&
。【参考方案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 &bar = (Ref&)c;
只会将一个 const 对象转换为该 const 对象的非常量引用。没有预防措施。
这就像说const
-正确性不存在,因为有人总是可以const
-围绕它进行转换(或者在你的情况下,C-style cast)。这是故意滥用,而不是 int &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 对象的主要内容,如果未能解决你的问题,请参考以下文章