指向不可变类型的共享指针具有值语义

35 c++ c++11

Sean Parent在Going Native 2013上发表了题为" 继承是邪恶的基础 "的演讲.在20分50秒时,他声明一个指向不可变(const)类型(std::shared_pointer<const T>)的共享指针具有值语义.这到底是什么意思?为什么它与可变(非常量)类型(std::shared_pointer<T>)的共享指针有什么不同?

Sea*_*ine 47

不幸的是,就像2013年Going Native的所有谈话一样,它受到时间紧迫的限制.幸运的是,对于我们来说,Sean Parent去年在C++上做了更全面的讨论,现在称为Value Semantics和基于概念的多态.它涵盖相同的材料,可能会回答您的问题.无论如何,我会先解释一下......

介绍

类型可以有两种类型的语义:

  • 价值语义.
  • 参考语义.(有时称为指针语义.)

关于两者如何不同以及哪一个优于另一个,可以继续许多页面.让我们简单地说使用值类型的代码可以更容易地被推理.

也就是说,在值类型的实例的任何位置都不会发生任何不可预测的事情 - 使用引用类型无法保证这一点,因为引用的值在代码的其他部分之间共享,这些部分包含对它的引用.

换句话说:引用类型不太可预测,因为它们可以通过远程代码进行更改.例如,您调用的函数可能会更改从您下面引用的值.或者,更糟糕的是,如果涉及线程,则可以随时通过对引用的值进行操作的另一个线程来更改引用类型.出于这个原因,Sean Parent 在能够推断使用一个代码的代码时,声明a shared_ptr和全局变量一样好.

尽管如此,我们应该准备好回答手头的问题.


问题和答案

对于值类型T,为什么shared_ptr<const T>即使它是指针类型,它也像值类型一样?

因为我们无法对const T指向的内容进行更改,所以关于指针/引用类型的可预测性不再适用.我们不再需要担心T被意外更改,因为它是一个const值类型.

如果我们不希望做出改变的T,我们将不得不作出的一个副本,让其他人持有shared_ptr<const T>我们的行动的影响.此外,复制甚至可以使用名为机制值类型藏在里面写入时复制,这似乎是什么肖恩家长最终做到了.


我想我已经回答了Sean Parent会提出的问题(并且在链接的C++ Now演示文稿中做过),但是让我们进一步了解附录.....

一个大附录:

(感谢@BretKuhns提出的建议并在评论中提供了一个例子.)

这整个概念有一个令人烦恼的问题.shared_ptr<const T>除非我们知道所有生命指针/对该实例的引用都是正确的,否则表示行为类似于值类型并不是必需Tconst.这是因为const修饰符是单行道 - 持有shared_ptr<const T>可能会阻止我们修改实例T,但不会阻止其他人T通过指针/引用修改非const.

知道了这一点,我会厌倦制作shared_ptr<const T>一个与价值类型一样好的广泛陈述,除非我知道所有生活指针都是如此const.但是,知道这样的事情需要围绕所有用法的代码的全局知识shared_ptr<const T>- 这对于值类型来说不是问题.出于这个原因,可能更有意义的说:A shared_ptr<const T>可以用来支持价值语义.


在旁注中,我实际上是在2013年的Going Native - 也许你可以在左前方看到我的后脑勺.

  • 请注意,这只是从库返回`shared_ptr <const Foo>`的角度来看.收到`shared_ptr <const Foo>`的客户端无法保证`Foo`的状态不会在外部发生变异.请参阅此代码段以获取示例用例:https://gist.github.com/bkuhns/6563448,即使我的`main()`包含`shared_ptr <const Foo>`,`Foo`的`num( )`改变状态.在多线程应用程序中,这可能会更加微妙,其中状态可能会更改,而不会像我的示例中那样本地调用`doSomething()`. (2认同)
  • 我意识到这是事后的事情,但我想观察一下:在附录中,你说"整个概念"有问题.那不太对劲.对于不可变对象的shared_ptr具有值语义是完全正确的.但const T与不可变对象不同,完全是出于评论中讨论的原因.const T是一种类型,指针/引用可以使用它来引用C++类型系统的非const T. 如果创建一个没有非const方法的类型T,那就是一个不可变类型,并且它的shared_ptr具有值语义. (2认同)

Ali*_*Ali 7

我举三个例子.在所有三种情况下,我都a使用内容创建变量"original value".然后我b通过说明auto b = a;并在此语句之后创建另一个变量来分配a内容"new value".

如果a并且b有价值语义,我希望它b的内容是"original content".事实上,这恰好发生在stringshared_ptr<const string>.概念含义auto b = a;与这些类型相同.没有那么多shared_ptr<string>,b会有内容"new value".

代码(在线演示):

#include <iostream>
#include <memory>
#include <string>

using namespace std;

void string_example() {

    auto a = string("original value");

    auto b = a; // true copy by copying the value

    a = string("new value");

    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}

void shared_ptr_example() {

    auto a = make_shared<string>("original value");

    auto b = a; // not a copy, just and alias

    *a = string("new value"); // and this gonna hurt b

    cout << "a = " << *a << endl;
    cout << "b = " << *b << endl;
    cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}

void shared_ptr_to_const_example() {

    auto a = make_shared<const string>("original value");

    auto b = a;

    //*a = string("new value"); // <-- now won't compile
    a = make_shared<const string>("new value");

    cout << "a = " << *a << endl;
    cout << "b = " << *b << endl;
    cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}

int main() {

    cout << "--------------" << endl;
    cout << "string example" << endl;
    string_example();

    cout << "------------------" << endl;
    cout << "shared_ptr example" << endl;
    shared_ptr_example();

    cout << "---------------------------" << endl;
    cout << "shared_ptr to const example" << endl;
    shared_ptr_to_const_example();
}
Run Code Online (Sandbox Code Playgroud)

输出:

--------------
string example
a = new value
b = original value
&a == &b ? false
------------------
shared_ptr example
a = new value
b = new value
&a == &b ? false
---------------------------
shared_ptr to const example
a = new value
b = original value
&a == &b ? false
Run Code Online (Sandbox Code Playgroud)

话虽如此,我希望他有更多的时间:在演讲结束后,我仍然想知道一些事情.我相信只是缺乏时间,他似乎是一位出色的主持人.


Cor*_*ica 5

他的意思是它们可以用来模拟价值语义.

值语义的主要定义特征是具有相同内容的两个对象是相同的.整数是值类型:a 5与任何其他类型相同5.将其与参考机制进行比较,其中对象具有标识. 列表a含有[1,2]是不一样的列表b含有[1,2],因为追加3至a不具有作为附加3相同的效果b.该标识a比不同身份b.

这往往是直观的...当用文字说话时听起来很奇怪.如果没有直观的价值类型与参考类型相比,没有人能在C++中使用3天.

如果您有一个可变值类型并且想要复制它,则必须实际复制该对象的内容.这很贵.

Sean所指的是,如果一个对象是不可变的,那么你不必复制整个对象,你只需要引用旧对象即可.这要快得多.