C++11 中的不可变"functional"数据结构

Immutable "functional" data structure in C++11

本文关键字:functional 数据结构 不可变 C++11      更新时间:2023-10-16

我试图写下我对多线程/并发场景感兴趣的几个数据结构的一些实现。

据我所知,很多函数式语言都以不可变的方式设计自己的数据结构,所以这意味着,如果你要将value添加到T的实例t1中,你会得到一个新的T实例,它包含t1 + value

container t;
container s = t; //t and s refer to the same container.
t.add(value); //this makes a copy of t, and t is the copy

我在C++11中找不到合适的关键字来做这件事;标准库中的关键字、语义和函数都明确地面向函数方法,特别是我发现:

  • mutable它不适用于运行时,它更可能是编译器的提示,但这个关键字并不能真正帮助您设计新的数据结构或以不可变的方式使用数据结构
  • swap不适用于临时性,这对我来说是一个很大的缺点

我也不知道其他关键字/函数对这样的设计有多大帮助,swap是其中一个非常接近好的东西,所以我至少可以开始写一些东西,但显然它仅限于lvalue

所以我想问:是否可以用函数方法在C++11中设计不可变的数据结构?

您只需用私有成员变量声明一个类,并且不提供任何方法来更改这些私有成员的值。就是这样。您只能从类的构造函数初始化成员。没有人能够通过这种方式更改类的数据。C++创建不可变对象的工具是成员的私有可见性。

mutable:这是C++中最大的技巧之一。在我的一生中,我最多见过两个地方它的使用是合理的,这个关键词与你正在搜索的几乎相反。如果您要在C++中搜索一个关键字,它可以帮助您在编译时标记数据成员,那么您正在搜索const关键字。如果将类成员标记为const,则只能从构造函数的INITIALIZER LIST对其进行初始化,并且在实例的整个生命周期内不能再对其进行修改。这不是C++11,它是纯C++。没有神奇的语言特性可以提供不变性,只有通过巧妙的编程才能做到这一点。

Inc++"不变性"由const关键字授予。当然-您仍然可以更改const变量,但您必须有目的地这样做(就像这里一样)。在正常情况下,编译器不会让你这么做。由于您最关心的似乎是以函数样式进行操作,并且您想要结构,因此您可以自己定义它:

class Immutable{
Immutable& operator=(const Immutable& b){} // This is private, so it can't be called from outside
const int myHiddenValue;
public:
operator const int(){return myHiddenValue;}
Immutable(int valueGivenUponCreation): myHiddenValue(valueGivenUponCreation){}
};

如果定义这样的类,即使尝试用const_cast更改myHiddenValue,它实际上也不会执行任何操作,因为在调用operator const int的过程中会复制该值。

注意:没有真正的理由这么做,但嘿,这是你的愿望。

另请注意:由于指针存在于C++中,您仍然可以使用某种指针魔术来更改值(获取对象的地址、计算偏移量等),但您对此无能为力。即使在使用函数式语言时,如果它有指针,你也无法防止这种情况的发生。

顺便说一句,你为什么要强迫自己以一种功能性的方式使用C++?我可以理解它对你来说更简单,而且你已经习惯了,但函数式编程并不经常使用,因为它有缺点。请注意,无论何时创建新对象,都必须分配空间。对于最终用户来说,速度要慢。

Bartoz-Milewski在C++中实现了Okasaki的函数数据结构。他对为什么函数数据结构对并发性很重要进行了非常深入的论述。在那篇论文中,他解释了在并发中构造一个对象,然后使其不可变的必要性:

以下是需要发生的事情:线程必须以某种方式构造指定为不可变的数据。取决于这些数据,这可能是一个非常简单或非常复杂的过程。然后该数据的状态必须被冻结--不再有任何更改允许。

正如其他人所说,当你想在C++中公开数据,但无法更改时,你可以让你的函数签名看起来像这样:

class MutableButExposesImmutably
{
private:
std::string member;
public:
void complicatedProcess() { member = "something else"; } // mutates
const std::string & immutableAccessToMember() const {
return member;
}
};

这是一个可变的数据结构示例,但不能直接对其进行变异。

我认为您正在寻找类似于java的final关键字的东西:这个关键字允许您构造一个对象,但此后该对象保持不变。

你可以在C++中做到这一点。编译以下代码示例。请注意,在类Immutable中,对象member实际上是不可变的(与上一个示例不同):您可以构造它,但一旦构造好,它就不可变了。

#include <iostream>
#include <string>
using namespace std;
class Immutable
{
private:
const std::string member;
public:
Immutable(std::string a) : member(a) {}
const std::string & immutable_member_view() const { return member; }
};

int main() {
Immutable foo("bar");
// your code goes here
return 0;
}

Re。您使用st的代码示例。你可以在C++中做到这一点,但如果我正确理解你的需求,"不变性"与这个问题无关!

我在供应商库中使用过容器,它们确实按照您描述的方式操作;即,当它们被复制时,它们共享内部数据,并且在需要更改其中一个数据之前,它们不会复制内部数据。

请注意,在您的代码示例中,有一个要求,即如果s发生更改,则t不得发生更改。因此,s必须包含某种标志或引用计数,以指示t当前正在共享其数据,因此当s的数据发生更改时,它需要剥离一个副本,而不仅仅是更新其数据。

因此,作为容器外观的一个非常宽泛的概述:它将由一个指向某些数据的句柄(例如指针)和一个引用计数组成;更新数据的函数都需要检查refcount来决定是否重新分配数据;并且您的复制构造函数和复制赋值运算符需要增加refcount。