为什么要复制const shared_ptr&不违反常规?

时间:2016-03-28 21:38:30

标签: c++ const shared-ptr

即使我的代码编译得很好,这也是困扰我的东西,而且我无法在stackoverflow上找到答案。以下泛型构造函数是将shared_ptr传递给构造函数中的类实例的一种方法。

MyClass {
  MyClass(const std::shared_ptr<const T>& pt);
  std::shared_ptr<const T> pt_;  //EDITED: Removed & typo
};

MyClass::MyClass(const std::shared_ptr<const T>& pt)
  : pt_(pt)
{ }

编译好。我的问题如下:在我的理解中,声明一个像这样的参数const:

void myfunc(const T& t)

承诺不改变。 但是,通过将shared_ptr pt复制到pt_,我是否有效地增加了shared_ptr pt的使用次数,从而违反了假定的常量?

这可能是对我这边的shared_ptrs的一个根本误解?

(对于任何阅读本文的人来说,请注意this可能是更好的实现方式)

6 个答案:

答案 0 :(得分:6)

std::shared_prt<>必须拥有的成员之一是旧式复制构造函数:

shared_ptr(const shared_ptr& r) noexcept;

标准说(C ++ 11 20.7.2.2.1 / 18“shared_ptr构造函数”)“如果r为空,则构造一个空的shared_ptr对象;否则,构造一个与r共享所有权的shared_ptr对象。” / p>

该标准未提及如何通过const参考完成“与r共享所有权”。一些选项可能是:

  • 实现共享所有权语义的私有成员可能标记为mutable
  • 实现共享所有权的数据结构可能实际上不在shared_ptr对象中 - 例如,它们可能是可能通过指针获取的单独对象集。

答案 1 :(得分:3)

您的问题的简单答案是,因为引用计数不存储在共享指针实例中,而是存储在保留引用计数的外部对象中。复制构造shared_ptr时,引用计数将添加到外部对象中。查看由Stephen T. Lavavej撰写的this lecture,其中解释了实施

答案 2 :(得分:3)

共享指针的概念布局如下:

shared_ptr包含指向对象的指针以及指向控制块的指针。它是控制块的生命周期的控制块,而不是shared_ptr对象本身,它只是一个包装器和一些代码来通知控制块已经增加或减少了引用的数量。它是控制块,它存储引用计数,删除器和指针指向原始接口的指针(因此删除器可以删除正确的接口,即使已经有指针转换)。

* shared_ptr object *
| pointer to object | ---------------> object
| pointer to control block |----+   +> (possibly different interface
                                |   |   but still same object)
                                |   |
* control block *    <----------+   |
| reference count   |               |
| deleter           |               |
| pointer to object | --------------+

由于shared_ptr的内存看起来像这样:

template<class T>
struct shared_ptr {
    T* ptr;
    control_block* pctrl;
};

应该开始显而易见的是,即使shared_ptr是const,获取副本也不需要对shared_ptr的内部状态进行任何变更。突变发生在控制块中,由shared_ptr 指向

因此合同没有被打破。就像你宣布

一样
T* const p;

修改p本身是不可能的,但修改(*p)是完全合理的。

答案 3 :(得分:2)

class CommentsController < ApplicationController before_action :set_post def create @comment = @post.comments.build(comment_params) @comment.user_id = current_user.id if @comment.save flash[:success] = "You commented the hell out of that post!" redirect_to :back else flash[:alert] = "There is a problem with your comment" render root_path end end def destroy @comment = @post.comments.find(params[:id]) @comment.destroy flash[:success] = "Comment deleted :(" redirect_to root_path end private def set_post @post = Post.find(params[:post_id]) end def comment_params params.require(:comment).permit(:content, :post_id, :user_id) end end ,在界面中,意味着它想要表达的意思。其含义应记录在案。

通常,它意味着“某些ny状态的子集不会改变”。对于共享ptr,不能改变的状态子集是“我指向的”。

计数可以改变。内容可以改变。

在C ++ std库中,const可以解释为“线程安全” - 因为如果const操作是线程安全的,并且将它们放在const容器中, std容器又具有线程安全的const操作。

通过线程安全,我不是指同步 - 我的意思是两个不同的线程,都做const的东西,是的。如果一个线程正在做非常量的东西,那么所有的赌注都会被关闭。

这允许简单的读写器锁定逻辑。

因为添加/删除ref确实是线程安全的,所以重新安装ptr不是......

答案 4 :(得分:1)

  

我的问题如下:在我的理解中,声明一个像这样的参数const ...承诺不会改变t。

不完全正确。承诺不是改变任何可观察的状态......大部分时间。 const对象有几种方式可以改变&#34;:

  1. 它有可变变量 - 这些变量意味着在const约束下发生变化,但设计方法说这些变量应该是罕见的,不应该是可观察的。它们的另一个常见用途是缓存计算成本高昂的东西。所以你有一个const函数get,可以进行大量的计算来返回一个值 - 你希望这个优化,以便你创建一个缓存。缓存必须在get调用期间更改,但实际上get始终返回相同的内容,因此没有人能够观察到对象的状态已更改。

  2. 它具有非常量指针或对其他对象的引用。在这些情况下,聚合,它不是改变的对象,而是其他东西。这是在shared_ptr的情况下发生的情况,它具有指向实际保存指针值的共享引用计数对象的指针。起初它不直观,因为这样一个对象的报告状态可能会改变,但实际上并不是对象本身发生了变化。这里的设计方法是逐个案例的,但除非你将指针声明为指向const的指针,否则语言绝不会保护你。

答案 5 :(得分:1)

传递shared_ptr作为参考不会增加其引用计数。此外,您不是在这里复制任何内容,只需要引用,因此引用计数保持不变。

请注意,使用对共享指针的引用作为类成员通常不是您想要的。这样,当你想要使用它时,你无法保证指针仍处于活动状态 - 这基本上是使用共享指针时的目标。

对您的编辑的响应:现在,通过使用共享指针成员,您确实创建了一个副本,从而增加了ref-count。这是可能的,因为您可以复制const对象。所以你提到的这个保证人不存在 - 基本上,关键字mutable阻止它。