为什么复制常量shared_ptr和不违反常量?
Why does copying a const shared_ptr& not violate const-ness?
尽管我的代码编译得很好,但这一直困扰着我,我在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)
承诺不会更改t.然而,通过将shared_ptr pt复制到pt_,我是否没有有效地增加shared_ptr-pt的使用次数,从而违反了所谓的constness
这可能是我对shared_ptrs的根本误解?
(对于任何想要实现它的人来说,请注意这可能是一个更好的实现)
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_pt对象"。
该标准没有提及如何通过引用const
来实现"与r共享所有权"。一些选项可能是:
- 实现共享所有权语义的私有成员可能被标记为
mutable
- 实现共享所有权的数据结构实际上可能并不存在于
shared_ptr
对象中——例如,它们可能是一组单独的对象,可以通过指针获得
共享指针的概念布局如下:
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)
是完全合理的。
问题的简单答案是no,因为引用计数不是存储在共享指针实例中,而是存储在负责保持引用计数的外部对象中。当您复制构造shared_ptr时,引用计数会添加到外部对象中。看看Stephen T.Lavavej的这篇讲座,它解释了
const
在接口中的实现,它意味着它想要的任何含义。其含义应记录在案。
通常,它意味着"ny状态的某个子集不会改变"。对于共享ptr,不能更改的状态子集是"我所指向的"。
计数可以更改。内容可以更改。
在C++标准库中,const
可以被解释为"线程安全"——因为如果const
操作是线程安全的,并且您将它们放在std
容器中,那么std
容器就有线程安全的常量操作。
所谓线程安全,我的意思不是同步——我的意思是两个不同的线程,都在做const的事情,这是可以的。如果一个线程正在做非常量的事情,那么所有的赌注都会被取消
这允许简单的读写器锁定逻辑。
由于添加/删除ref确实是线程安全的,而重新封装ptr则不是。。。
我的问题如下:在我的理解中,声明这样的参数const。。。承诺不会改变t.
不完全正确。承诺是不改变任何可观察到的状态。。。大多数时候。const对象可以通过几种方式"更改":
-
它有可变的变量——这些变量是在const约束下改变的,但设计方法论认为这些变量应该是罕见的,不应该是可观察的。它们的一个更常见的用途是缓存计算成本高昂的东西。因此,您有一个常量函数
get
,它进行大量计算以返回一个值——您希望对其进行优化,以便创建缓存。在get
调用期间,缓存必须更改,但实际上get
总是返回相同的东西,因此没有人可以观察到对象的状态已经更改。 -
它具有指向其他对象的非常量指针或引用。在这些情况下,聚合不是对象发生了变化,而是其他东西发生了变化。这就是
shared_ptr
的情况,它有一个指向共享引用计数对象的指针,该对象实际保存指针的值。一开始这是不直观的,因为这样一个对象的报告状态可能会改变,但实际上并不是对象本身改变了。这里的设计方法论是基于具体情况的,但除非您将指针声明为指向const的指针,否则该语言不会保护您。
将shared_ptr
作为引用传递不会增加其引用计数。此外,您在这里没有复制任何内容,您只是获取引用,因此引用计数保持不变。
请注意,使用对共享指针的引用作为类成员通常不是您想要的。通过这种方式,您无法保证当您想要使用指针时指针仍然有效——这基本上是使用共享指针时的目标。
对您的编辑的响应:现在,通过使用共享指针成员,您确实创建了一个副本,从而增加了引用计数。这是可能的,因为您可以复制常量对象。所以你提到的这个guaraantee不存在——本质上,关键字mutable
阻止了它
- #定义c-预处理器常量..我做错了什么
- 用C++中的一个变量定义一个常量
- 什么时候在C++中返回常量引用是个好主意
- 代理对象的常量正确性
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 通过多个头文件使用常量变量
- 在cuda线程之间共享大量常量数据
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 是默认情况下分配给char数组常量的值
- 私有类型的静态常量成员
- 类似枚举的计算常量
- 递归模板化函数不能分配给具有常量限定类型"const tt &"的变量"state"
- 为什么我可以通过引用修改常量返回
- 如何创建长度由常量参数指定的数组
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- 为什么在将常量 ptr 分配给常量引用时没有收到编译错误?
- 为什么(常量字符*)ptr 不被视为左值
- 如何将常量字符* []隐藏到单个内存块,例如char* ptr
- 为什么非常量ptr不能隐式地将ptr转换为常量作为模板中的参数