C++ 中的移动语义是否缺少 C 所缺少的东西?
Is move semantics in C++ something C is missing?
我一直在 SO 和其他来源上搜索这个问题,但我无法解决这个问题。使用右值和 xvalues 的资源对于 C++(对于 C++11)来说有点新。
现在,我们 - C 程序员 - 是否遗漏了什么?或者 C 中有相应的技术可以从这些资源效率中受益?
编辑: 这个问题完全不是基于意见的。我只是无法描述我的问题。我想问的是c中有没有对应的技巧
当然,在 C 中也有类似的技术。我们在 C 中已经 "move semantics" 很久了。
首先,C++ 中的 "move semantics" 基于一系列重载决议规则,这些规则描述了具有右值引用参数的函数在重载决议期间的行为方式。由于 C 不支持函数重载,因此这个具体问题不适用于 C。您仍然可以在 C 中手动实现移动语义,方法是编写具有专用名称的专用数据移动函数,并在您想要移动数据时显式调用它们,而不是复制它。例如,对于您自己的数据类型 struct HeavyStruct
,您可以编写具有适当实现的 copy_heavy_struct(dst, src)
和 move_heavy_struct(dst, src)
函数。在每种情况下,您只需手动选择最多 appropriate/efficient 个来调用。
其次,C++ 中隐式移动语义的主要目的是在深度复制不必要地低效的情况下作为隐式深度复制语义的替代方法。由于 C 没有隐式的深拷贝语义,所以在 C 中甚至不会出现这个问题。C 总是执行浅拷贝,这已经非常类似于移动语义。基本上,您可以将 C 视为一种永远在变化的语言。它只需要一些手动调整即可使其移动语义完美。
当然,从字面上重现 C++ 移动语义的所有功能可能是不可能的,因为,例如,不可能将 C 指针绑定到右值。但实际上一切都可以 "emulated"。它只是需要做更多的工作 explicitly/manually.
相当于 C++ 移动语义的 C 将按值传递一个 struct
,然后继续丢弃原始对象而不破坏它,依赖于副本的破坏是正确的。
然而,这在 C 中非常容易出错,因此通常会避免。我们在 C 中实际执行的最接近移动语义的是当我们在结构数组上调用 realloc()
时,依赖于按位复制等同于原始副本。同样,原件既没有被破坏也没有被再次使用。
C 风格复制和 C++ 移动语义之间的区别在于,移动语义 修改 原始语义,以便可以安全地调用其析构函数。使用 C 的按位复制方法,我们只是忘记了原始内容,而不是对其调用析构函数。
这些更严格的语义使 C++ 移动语义比 C 风格的复制和遗忘更容易和更安全地使用。 C++ 移动语义的唯一缺点是,它比 C 风格的复制和遗忘方法稍慢:移动语义按元素而不是按位复制,然后继续修改原始的,这样析构函数就变成了语义 noop(尽管如此,它是仍然叫)。 C 风格复制并忘记用简单的 memcpy()
.
替换所有这些
我不认为 C 缺少移动语义。这是导致移动语义的所有 C++ 功能,在 C 中是 "missing"。由于您不能执行调用函数分配内存的自动结构复制,因此您没有自动复制复杂且昂贵的数据的系统结构。
当然是这么想的。 C 是一种比 C++ 更轻量级的语言,因此创建自定义复制和赋值构造函数的复杂性并不意味着成为该语言的一部分——您只需编写代码来完成需要作为函数完成的事情。如果你想要 "deep copy",那么你写一些东西来遍历你的数据结构并分配内存等。如果你想要浅拷贝,你写一些东西来将数据结构中的指针复制到另一个数据结构(并且可能设置来源 NULL
) - 就像移动语义构造函数一样。
当然,你只需要C中的L和R值(它在=号的左边或右边),没有引用,显然没有R值引用。这是在 C 中通过使用地址(将事物变成指针)来实现的。
因此,C 缺少的并不是真正的移动语义,而是 C++ 语言设计附带的复杂构造函数和赋值运算符(等)使移动语义在该语言中成为有用的东西。像往常一样,语言根据它们的特性而发展。如果您没有功能 A,而功能 B 取决于功能 A 的存在,那么您就没有 "need" 功能 B。
当然,除了异常处理和 const 引用 [以及 C++11 中的 R 值引用,它本质上是允许您修改的 const 引用],我认为没有什么主要的C++ 中无法通过 C 实现的功能。它有时有点笨拙和混乱(并且在语法上不会那么漂亮,并且当您以错误的方式重写函数时编译器不会给您简洁的错误消息,你'将需要手动投射指针等)。 [在这样陈述之后,有人会指出 "you obviously didn't think of X",但整体陈述仍然正确 - C 可以完成您想在 C 中完成的 99.9%]
C 没有直接等同于移动语义,但是移动语义在 C++ 中解决的问题在 C 中不太常见:
由于 c 也没有复制构造函数/赋值运算符,因此默认情况下副本是浅的,而在 c++ 中,通常的做法是将它们实现为深复制操作或首先阻止它们。
C 也没有析构函数和 RAII 模式,因此转移资源所有权的频率较低。
没有。你必须自己动手,但像 C++ 的其他特性(例如多态性)一样,你可以实现相同的语义,但需要更多编码:
#include<stdlib.h>
typedef struct {
size_t cap;
size_t len;
int* data;
} vector ;
int create_vector(vector *vec,size_t init_cap){
vec->data=malloc(sizeof(int)*init_cap);
if(vec->data==NULL){
return 1;
}
vec->cap=init_cap;
vec->len=0;
return 0;
}
void move_vector(vector* to,vector* from){
//This effects a move...
to->cap=from->cap;
to->len=from->len;
free(to->data);
to->data=from->data;//This is where the move explicitly takes place.
//Can't call destroy_vec() but need to make the object 'safe' to destroy.
from->data=NULL;
from->cap=0;
from->len=0;
}
void destroy_vec(vector *vec){
free(vec->data);
vec->data=NULL;
vec->cap=0;
vec->len=0;
}
请注意 move_vector()
中的 data
是如何(嗯……)从一个向量移动到另一个向量的。
在对象之间处理资源的想法在 C 中很常见,最终达到 'move semantics'。 C++ 刚刚将其形式化、清理并将其合并到重载中。
你甚至可能自己做过但没有意识到,因为你没有给它起名字。资源的 'owner' 发生变化的任何地方都可以解释为 'move semantics'.
我一直在 SO 和其他来源上搜索这个问题,但我无法解决这个问题。使用右值和 xvalues 的资源对于 C++(对于 C++11)来说有点新。
现在,我们 - C 程序员 - 是否遗漏了什么?或者 C 中有相应的技术可以从这些资源效率中受益?
编辑: 这个问题完全不是基于意见的。我只是无法描述我的问题。我想问的是c中有没有对应的技巧
当然,在 C 中也有类似的技术。我们在 C 中已经 "move semantics" 很久了。
首先,C++ 中的 "move semantics" 基于一系列重载决议规则,这些规则描述了具有右值引用参数的函数在重载决议期间的行为方式。由于 C 不支持函数重载,因此这个具体问题不适用于 C。您仍然可以在 C 中手动实现移动语义,方法是编写具有专用名称的专用数据移动函数,并在您想要移动数据时显式调用它们,而不是复制它。例如,对于您自己的数据类型 struct HeavyStruct
,您可以编写具有适当实现的 copy_heavy_struct(dst, src)
和 move_heavy_struct(dst, src)
函数。在每种情况下,您只需手动选择最多 appropriate/efficient 个来调用。
其次,C++ 中隐式移动语义的主要目的是在深度复制不必要地低效的情况下作为隐式深度复制语义的替代方法。由于 C 没有隐式的深拷贝语义,所以在 C 中甚至不会出现这个问题。C 总是执行浅拷贝,这已经非常类似于移动语义。基本上,您可以将 C 视为一种永远在变化的语言。它只需要一些手动调整即可使其移动语义完美。
当然,从字面上重现 C++ 移动语义的所有功能可能是不可能的,因为,例如,不可能将 C 指针绑定到右值。但实际上一切都可以 "emulated"。它只是需要做更多的工作 explicitly/manually.
相当于 C++ 移动语义的 C 将按值传递一个 struct
,然后继续丢弃原始对象而不破坏它,依赖于副本的破坏是正确的。
然而,这在 C 中非常容易出错,因此通常会避免。我们在 C 中实际执行的最接近移动语义的是当我们在结构数组上调用 realloc()
时,依赖于按位复制等同于原始副本。同样,原件既没有被破坏也没有被再次使用。
C 风格复制和 C++ 移动语义之间的区别在于,移动语义 修改 原始语义,以便可以安全地调用其析构函数。使用 C 的按位复制方法,我们只是忘记了原始内容,而不是对其调用析构函数。
这些更严格的语义使 C++ 移动语义比 C 风格的复制和遗忘更容易和更安全地使用。 C++ 移动语义的唯一缺点是,它比 C 风格的复制和遗忘方法稍慢:移动语义按元素而不是按位复制,然后继续修改原始的,这样析构函数就变成了语义 noop(尽管如此,它是仍然叫)。 C 风格复制并忘记用简单的 memcpy()
.
我不认为 C 缺少移动语义。这是导致移动语义的所有 C++ 功能,在 C 中是 "missing"。由于您不能执行调用函数分配内存的自动结构复制,因此您没有自动复制复杂且昂贵的数据的系统结构。
当然是这么想的。 C 是一种比 C++ 更轻量级的语言,因此创建自定义复制和赋值构造函数的复杂性并不意味着成为该语言的一部分——您只需编写代码来完成需要作为函数完成的事情。如果你想要 "deep copy",那么你写一些东西来遍历你的数据结构并分配内存等。如果你想要浅拷贝,你写一些东西来将数据结构中的指针复制到另一个数据结构(并且可能设置来源 NULL
) - 就像移动语义构造函数一样。
当然,你只需要C中的L和R值(它在=号的左边或右边),没有引用,显然没有R值引用。这是在 C 中通过使用地址(将事物变成指针)来实现的。
因此,C 缺少的并不是真正的移动语义,而是 C++ 语言设计附带的复杂构造函数和赋值运算符(等)使移动语义在该语言中成为有用的东西。像往常一样,语言根据它们的特性而发展。如果您没有功能 A,而功能 B 取决于功能 A 的存在,那么您就没有 "need" 功能 B。
当然,除了异常处理和 const 引用 [以及 C++11 中的 R 值引用,它本质上是允许您修改的 const 引用],我认为没有什么主要的C++ 中无法通过 C 实现的功能。它有时有点笨拙和混乱(并且在语法上不会那么漂亮,并且当您以错误的方式重写函数时编译器不会给您简洁的错误消息,你'将需要手动投射指针等)。 [在这样陈述之后,有人会指出 "you obviously didn't think of X",但整体陈述仍然正确 - C 可以完成您想在 C 中完成的 99.9%]
C 没有直接等同于移动语义,但是移动语义在 C++ 中解决的问题在 C 中不太常见: 由于 c 也没有复制构造函数/赋值运算符,因此默认情况下副本是浅的,而在 c++ 中,通常的做法是将它们实现为深复制操作或首先阻止它们。
C 也没有析构函数和 RAII 模式,因此转移资源所有权的频率较低。
没有。你必须自己动手,但像 C++ 的其他特性(例如多态性)一样,你可以实现相同的语义,但需要更多编码:
#include<stdlib.h>
typedef struct {
size_t cap;
size_t len;
int* data;
} vector ;
int create_vector(vector *vec,size_t init_cap){
vec->data=malloc(sizeof(int)*init_cap);
if(vec->data==NULL){
return 1;
}
vec->cap=init_cap;
vec->len=0;
return 0;
}
void move_vector(vector* to,vector* from){
//This effects a move...
to->cap=from->cap;
to->len=from->len;
free(to->data);
to->data=from->data;//This is where the move explicitly takes place.
//Can't call destroy_vec() but need to make the object 'safe' to destroy.
from->data=NULL;
from->cap=0;
from->len=0;
}
void destroy_vec(vector *vec){
free(vec->data);
vec->data=NULL;
vec->cap=0;
vec->len=0;
}
请注意 move_vector()
中的 data
是如何(嗯……)从一个向量移动到另一个向量的。
在对象之间处理资源的想法在 C 中很常见,最终达到 'move semantics'。 C++ 刚刚将其形式化、清理并将其合并到重载中。
你甚至可能自己做过但没有意识到,因为你没有给它起名字。资源的 'owner' 发生变化的任何地方都可以解释为 'move semantics'.