D 是否具有类似于 C++0x' 的移动语义?

发布于 2024-10-03 01:09:39 字数 224 浏览 1 评论 0原文

外部资源(例如 std::vectorstd::string)的“值类型”问题是复制它们往往非常昂贵,并且副本是在各种上下文中隐式创建的,因此这往往是一个性能问题。 C++0x 对这个问题的回答是移动语义,它在概念上基于资源窃取的思想,并且在技术上由右值引用提供支持。

D 有类似移动语义或右值引用的东西吗?

A problem of "value types" with external resources (like std::vector<T> or std::string) is that copying them tends to be quite expensive, and copies are created implicitly in various contexts, so this tends to be a performance concern. C++0x's answer to this problem is move semantics, which is conceptionally based on the idea of resource pilfering and technically powered by rvalue references.

Does D have anything similar to move semantics or rvalue references?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

梨涡 2024-10-10 01:09:39

我相信 D 中有几个地方(例如返回结构)D 设法使它们移动,而 C++ 会使它们成为副本。 IIRC,在任何情况下,编译器可以确定不需要复制时,都会执行移动而不是复制,因此结构复制在 D 中发生的次数比在 C++ 中少。当然,由于类是引用,因此它们根本不存在问题。

但无论如何,复制构造在 D 中的工作方式已经与在 C++ 中不同。通常,您不声明复制构造函数,而是声明 postblit 构造函数:this(this)。它在调用 this(this) 之前执行完整的 memcpy,并且您只需进行必要的更改以确保新结构与原始结构分离(例如对成员变量进行深层复制,其中需要),而不是创建一个必须复制所有内容的全新构造函数。所以,一般方法已经与 C++ 有点不同了。人们普遍认为结构不应该有昂贵的 postblit 构造函数 - 复制结构应该很便宜 - 因此它比 C++ 中的问题要小。复制成本高昂的对象通常是具有引用或 COW 语义的类或结构。

容器通常是引用类型(在 Phobos 中,它们是结构而不是类,因为它们不需要多态性,但复制它们不会复制它们的内容,因此它们仍然是引用类型),因此复制它们并不昂贵就像在 C++ 中一样。

在 D 中很可能有一些情况,它可以使用类似于移动构造函数的东西,但一般来说,D 的设计方式是为了减少 C++ 在复制对象时遇到的问题,所以它远没有解决问题它是用 C++ 编写的。

I believe that there are several places in D (such as returning structs) that D manages to make them moves whereas C++ would make them a copy. IIRC, the compiler will do a move rather than a copy in any case where it can determine that a copy isn't needed, so struct copying is going to happen less in D than in C++. And of course, since classes are references, they don't have the problem at all.

But regardless, copy construction already works differently in D than in C++. Generally, instead of declaring a copy constructor, you declare a postblit constructor: this(this). It does a full memcpy before this(this) is called, and you only make whatever changes are necessary to ensure that the new struct is separate from the original (such as doing a deep copy of member variables where needed), as opposed to creating an entirely new constructor that must copy everything. So, the general approach is already a bit different from C++. It's also generally agreed upon that structs should not have expensive postblit constructors - copying structs should be cheap - so it's less of an issue than it would be in C++. Objects which would be expensive to copy are generally either classes or structs with reference or COW semantics.

Containers are generally reference types (in Phobos, they're structs rather than classes, since they don't need polymorphism, but copying them does not copy their contents, so they're still reference types), so copying them around is not expensive like it would be in C++.

There may very well be cases in D where it could use something similar to a move constructor, but in general, D has been designed in such a way as to reduce the problems that C++ has with copying objects around, so it's nowhere near the problem that it is in C++.

空‖城人不在 2024-10-10 01:09:39

我认为所有答案完全没有回答原来的问题。

首先,如上所述,该问题仅与结构相关。班级没有任何有意义的举动。如上所述,对于结构体,编译器在某些条件下会自动发生一定量的移动。

如果您希望控制移动操作,您必须执行以下操作。您可以通过使用 @disable 注释 this(this) 来禁用复制。接下来,您可以通过定义 this(Struct that) 来重写 C++ 的 constructor(constructor &&that)。同样,您可以使用 opAssign(Struct that) 覆盖分配。在这两种情况下,您都需要确保销毁 that 的值。

对于赋值,由于还需要销毁 this 的旧值,所以最简单的方法就是交换它们。因此,C++ 的 unique_ptr 的实现看起来像这样:

struct UniquePtr(T) {
    private T* ptr = null;

    @disable this(this); // This disables both copy construction and opAssign

    // The obvious constructor, destructor and accessor
    this(T* ptr) {
        if(ptr !is null)
            this.ptr = ptr;
    }

    ~this() {
        freeMemory(ptr);
    }

    inout(T)* get() inout {
        return ptr;
    }

    // Move operations
    this(UniquePtr!T that) {
        this.ptr = that.ptr;
        that.ptr = null;
    }

    ref UniquePtr!T opAssign(UniquePtr!T that) { // Notice no "ref" on "that"
        swap(this.ptr, that.ptr); // We change it anyways, because it's a temporary
        return this;
    }
}

编辑:
请注意,我没有定义 opAssign(ref UniquePtr!T that)。这就是复制赋值运算符,如果您尝试定义它,编译器将出错,因为您在 @disable 行中声明了您没有这样的东西。

I think all answers completely failed to answer the original question.

First, as stated above, the question is only relevant for structs. Classes have no meaningful move. Also stated above, for structs, a certain amount of move will happen automatically by the compiler under certain conditions.

If you wish to get control over the move operations, here's what you have to do. You can disable copying by annotating this(this) with @disable. Next, you can override C++'s constructor(constructor &&that) by defining this(Struct that). Likewise, you can override the assign with opAssign(Struct that). In both cases, you need to make sure that you destroy the values of that.

For assignment, since you also need to destroy the old value of this, the simplest way is to swap them. An implementation of C++'s unique_ptr would, therefore, look something like this:

struct UniquePtr(T) {
    private T* ptr = null;

    @disable this(this); // This disables both copy construction and opAssign

    // The obvious constructor, destructor and accessor
    this(T* ptr) {
        if(ptr !is null)
            this.ptr = ptr;
    }

    ~this() {
        freeMemory(ptr);
    }

    inout(T)* get() inout {
        return ptr;
    }

    // Move operations
    this(UniquePtr!T that) {
        this.ptr = that.ptr;
        that.ptr = null;
    }

    ref UniquePtr!T opAssign(UniquePtr!T that) { // Notice no "ref" on "that"
        swap(this.ptr, that.ptr); // We change it anyways, because it's a temporary
        return this;
    }
}

Edit:
Notice I did not define opAssign(ref UniquePtr!T that). That is the copy assignment operator, and if you try to define it, the compiler will error out because you declared, in the @disable line, that you have no such thing.

冰雪之触 2024-10-10 01:09:39

D 具有单独的值和对象语义:

  • 如果您将类型声明为 struct,则默认情况下它将具有值语义;
  • 如果您将类型声明为 class,它将具有对象语义。

现在,假设您不自己管理内存,因为这是 D 中的默认情况 - 使用垃圾收集器 - 您必须了解声明为 class 的类型的对象自动是指针(或“引用”) “如果你愿意的话)是真实的物体,而不是真实的物体本身。

因此,当在 D 中传递向量时,您传递的是引用/指针。自动地。不涉及任何副本(除了参考文献的副本)。

这就是为什么 D、C#、Java 和其他语言“不需要”移动语义(因为大多数类型是对象语义,并且通过引用而不是通过复制进行操作)。

也许他们可以实施它,我不确定。但它们真的能像 C++ 那样获得性能提升吗?从本质上来说,这似乎不太可能。

D have separate value and object semantics :

  • if you declare your type as struct, it will have value semantic by default
  • if you declare your type as class, it will have object semantic.

Now, assuming you don't manage the memory yourself, as it's the default case in D - using a garbage collector - you have to understand that object of types declared as class are automatically pointers (or "reference" if you prefer) to the real object, not the real object itself.

So, when passing vectors around in D, what you pass is the reference/pointer. Automatically. No copy involved (other than the copy of the reference).

That's why D, C#, Java and other language don't "need" moving semantic (as most types are object semantic and are manipulated by reference, not by copy).

Maybe they could implement it, I'm not sure. But would they really get performance boost as in C++? By nature, it don't seem likely.

下雨或天晴 2024-10-10 01:09:39

我有一种感觉,实际上右值引用和“移动语义”的整个概念是在 C++ 中创建本地“临时”堆栈对象是正常的结果。 在 D 和大多数 GC 语言中,最常见的情况是在堆上放置对象,然后在通过调用堆栈返回临时对象时多次复制(或移动)临时对象就不会产生任何开销 - 所以也不需要一种机制来避免这种开销。

在 D(和大多数 GC 语言)中,class 对象永远不会隐式复制,并且大多数时候您只是传递引用,因此这可能意味着您不他们不需要任何右值引用。

OTOH,struct 对象不应该是“资源句柄”,而是行为类似于内置类型的简单值类型 - 所以再说一遍,这里没有任何移动语义的理由,恕我直言。

这将得出一个结论 - D 没有右值引用,因为它不需要它们

但是,我在实践中没有使用过右值引用,我只是阅读过它们,所以我可能跳过了此功能的一些实际用例。请将这篇文章视为对此事的一系列想法,希望对您有所帮助,而不是作为可靠的判断。

I somehow have the feeling that actually the rvalue references and the whole concept of "move semantics" is a consequence that it's normal in C++ to create local, "temporary" stack objects. In D and most GC languages, it's most common to have objects on the heap, and then there's no overhead with having a temporary object copied (or moved) several times when returning it through a call stack - so there's no need for a mechanism to avoid that overhead too.

In D (and most GC languages) a class object is never copied implicitly and you're only passing the reference around most of the time, so this may mean that you don't need any rvalue references for them.

OTOH, struct objects are NOT supposed to be "handles to resources", but simple value types behaving similar to builtin types - so again, no reason for any move semantics here, IMHO.

This would yield a conclusion - D doesn't have rvalue refs because it doesn't need them.

However, I haven't used rvalue references in practice, I've only had a read on them, so I might have skipped some actual use cases of this feature. Please treat this post as a bunch of thoughts on the matter which hopefully would be helpful for you, not as a reliable judgement.

属性 2024-10-10 01:09:39

我认为如果您需要来源来释放资源,您可能会遇到麻烦。然而,通过 GC,您通常可以避免需要担心多个所有者,因此在大多数情况下这可能不是问题。

I think if you need the source to loose the resource you might be in trouble. However being GC'ed you can often avoid needing to worry about multiple owners so it might not be an issue for most cases.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文