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

Posted

技术标签:

【中文标题】从 const 引用初始化非常量对象时防止复制【英文标题】:Prevent copying when initializing an non-const object from a const reference 【发布时间】:2010-11-29 04:27:57 【问题描述】:

我现在对 C++ 引用语义有些困惑。假设我有一个返回 const 引用的类:

class foo

private:
    std::map<int, int> stuff;
public:
    const std::map<int, int>& getStuff()
    
        return stuff;
    
;

我使用它如下:

foo f;
const std::map<int, int>& s = f.getStuff();

这很好,但如果我按如下方式使用它:

foo f;
std::map<int, int> s = f.getStuff();

究竟发生了什么?

如果我理解正确,则返回了对 stuff 的 const 引用,并在 s 中创建了一个副本,我可以对其造成严重破坏。有什么办法可以避免吗?

编辑:

所以没有办法避免在这里调用复制构造函数,因为 std::map 无论如何......

【问题讨论】:

AFAIK 复制构造函数将被调用,我看不出有办法避免这种情况。 你是对的。但是如果你想阻止自己这样做,你总是可以让s const 。还使 s 成为 const ref 意味着您需要确保 f 在此期间保持活动状态 - 如果它是先前声明的自动变量,这将自动发生,但如果它需要管理是在堆上创建的。 但是你对s 进行的任何修改都不会影响到原来的对象stuff。那么它对您有何影响? 不返回对地图的引用,为什么不直接将 const_iterators 返回到 begin() 和 end()?问题解决了。无需编写一堆包装代码。 @John:如果 OP 想要使用 std::map 特定功能(如 upper,lower_boundfind),这并不是一个真正可用的解决方案。 【参考方案1】:

简短回答:不,你无法阻止它。客户端不能修改原始文件,但是如果您授予客户端对地图的读取权限,那么客户端有责任不要对信息做愚蠢的事情;班级不可能阻止这种情况。

更长的答案:也许,但不是真的。如果您真的想使复制变得困难,您可以将映射包装在具有私有复制构造函数和赋值运算符的类中。这样s 赋值将是非法的(被编译器拒绝)。客户端仍然可以零碎地读取地图的元素并用它们填充新地图 - 手动副本 - 但防止这种情况的唯一方法是限制包装类中的读取访问,哪种违背了getStuff 的目的。

【讨论】:

一个包装器!是的,这正是我需要的,谢谢!!!今晚我的大脑完全炸了:s【参考方案2】:
std::map<int, int> s = f.getStuff();

这会调用std::map&lt;int, int&gt; 复制构造函数并制作对象的副本。将stuff 映射的内容复制到新的映射s

您不能对原始对象造成严重破坏,因为s 是一个与原始对象完全无关的新对象,除了原始对象和新对象具有相同的内容这一事实之外。

不可能通过foo::getStuff() 返回的常量引用合法地破坏stuff 映射。修改映射的唯一方法是通过const_cast,通过const_cast 获得的指针或引用修改对象可能会产生未定义的行为。

【讨论】:

令人惊讶的是这么多票,尽管它没有回答“有什么办法可以避免这种情况吗?”的问题 很公平,我想要么使用参考,要么转到 const std::map&lt;int, int&gt; s = f.getStuff(); 然后 @Chubsdad:不,我正在处理 OP 的元问题,即“这行代码到底是如何工作的?”一旦您发现原始对象被完整复制并且新对象除了具有相同的内容之外与原始对象完全无关,它应该很简单。 @vic:对。它是否可以修改它制作的副本应该取决于调用代码。如果调用者想要调用foo::getStuff(),制作地图副本并破坏副本,foo 类应该不在乎。【参考方案3】:

是的,你的理解是正确的。这与复制初始化无关,并且涉及复制构造函数的使用。无法避免此副本,因为这是您显示的代码 sn-p 所要求的。

即使您对副本造成严重破坏,也不要担心。原来的还是安全的。问题在于创建副本的过程是否会造成严重破坏,但那是另一个问题。

C++ 03相关参考资料:

$8.5/12-“初始化 发生在参数传递、函数中 返回,抛出异常(15.1), 处理异常(15.3),和 大括号括起来的初始值设定项列表 (8.5.1) 称为复制初始化 并且等价于 T x = 一个;"

$8.5/14-"如果初始化是 直接初始化,或者如果是 复制初始化,其中 cv-不合格版本的源代码 类型是相同的类,或 的派生类, 的类 目的地,构造函数是 经过考虑的。适用的 枚举构造函数 (13.3.1.3),然后选择最好的一个 通过重载决议(13.3)。 如此选择的构造函数被调用 初始化对象,使用 初始化表达式作为其 论点。如果没有构造函数 适用,或重载决议是 模棱两可,初始化是 格式不正确。”

【讨论】:

所以,如果 OP 对引用语义有点困惑,引用标准可能有点霸道......【参考方案4】:

是的,这将创建地图的副本。

至于你的问题 - 取决于你想避免多少。一般来说,如果这是你自己声明的类,你可以将复制构造函数或operator =设为私有,以防止它被使用,但显然,这会禁止你做很多事情。

【讨论】:

以上是关于从 const 引用初始化非常量对象时防止复制的主要内容,如果未能解决你的问题,请参考以下文章

拷贝构造函数详解

拷贝构造函数详解

“非常量引用的初始值必须为左值“及“匿名对象“

顶层const和底层const

笔记十:复制构造函数深拷贝浅拷贝

c++产生非常量引用的初始值必须是左值