移动构造函数和静态数组

发布于 2024-12-11 11:45:21 字数 1147 浏览 0 评论 0原文

我一直在探索 C++ 中 移动构造函数 的可能性,并且我想知道在下面的示例中有哪些方法可以利用此功能。考虑以下代码:

template<unsigned int N>
class Foo {
public:
    Foo() {
        for (int i = 0; i < N; ++i) _nums[i] = 0;
    }

    Foo(const Foo<N>& other) {
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) {
        // ??? How can we take advantage of move constructors here?
    }

    // ... other methods and members

    virtual ~Foo() { /* no action required */ }

private:
    int _nums[N];
};

Foo<5> bar() {
    Foo<5> result;
    // Do stuff with 'result'
    return result;
}

int main() {
    Foo<5> foo(bar());
    // ...
    return 0;
}

在上面的示例中,如果我们跟踪程序(使用 MSVC++ 2011),我们会看到在以下情况下调用 Foo::Foo(Foo&&)构造 foo,这是所需的行为。但是,如果我们没有 Foo::Foo(Foo&&),则 Foo::Foo(const Foo& ;) 将被调用,这将执行冗余复制操作。

我的问题是,正如代码中所述,在这个使用静态分配的简单数组的特定示例中,有没有办法利用移动构造函数来避免这种冗余复制?

I've been exploring the possibilities of Move Constructors in C++, and I was wondering what are some ways of taking advantage of this feature in an example such as below. Consider this code:

template<unsigned int N>
class Foo {
public:
    Foo() {
        for (int i = 0; i < N; ++i) _nums[i] = 0;
    }

    Foo(const Foo<N>& other) {
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) {
        // ??? How can we take advantage of move constructors here?
    }

    // ... other methods and members

    virtual ~Foo() { /* no action required */ }

private:
    int _nums[N];
};

Foo<5> bar() {
    Foo<5> result;
    // Do stuff with 'result'
    return result;
}

int main() {
    Foo<5> foo(bar());
    // ...
    return 0;
}

In this above example, if we trace the program (with MSVC++ 2011), we see that Foo<N>::Foo(Foo<N>&&) is called when constructing foo, which is the desired behaviour. However, if we didn't have Foo<N>::Foo(Foo<N>&&), Foo<N>::Foo(const Foo<N>&) would be called instead, which would do a redundant copy operation.

My question is, as noted in the code, with this specific example which is using a statically-allocated simple array, is there any way to utilize the move constructor to avoid this redundant copy?

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

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

发布评论

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

评论(4

甚是思念 2024-12-18 11:45:21

首先,有一个一般性的建议,如果可以的话,你根本不应该编写任何复制/移动构造函数、赋值运算符或析构函数,而是组成你的高质量类组件反过来提供这些,允许默认生成的函数做正确的事情。 (相反的含义是,如果您确实必须编写其中任何一个,则可能必须编写所有这些。)

因此问题归结为“哪个单一职责组件类可以利用移动语义?”一般答案是:任何管理资源的东西。要点是,移动构造函数/分配器只会将资源重新安置到新对象并使旧对象无效,从而避免(假定昂贵或不可能的)资源的新分配和深度复制。

主要的例子是管理动态内存的任何东西,其中移动操作只是复制指针并将旧对象的指针设置为零(因此旧对象的析构函数不执行任何操作)。这是一个简单的例子:

class MySpace
{
  void * addr;
  std::size_t len;

public:
  explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }

  ~MySpace() { ::operator delete(addr); }

  MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
  { /* copy memory */ }

  MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
  { rhs.len = 0; rhs.addr = 0; }

  // ditto for assignment
};

关键是任何复制/移动构造函数都会对成员变量进行完整复制;只有当这些变量本身是资源的句柄或指针时,您才能避免复制资源,因为移动的对象不再被视为有效并且您可以随意窃取它。如果没有什么可偷的,那么搬家就没有任何好处。

First off, there's a general sort of advice that says you shouldn't write any copy/move constructor, assignment operator or destructor at all if you can help it, and rather compose your class of high-quality components which in turn provide these, allowing the default-generated functions to Do The Right Thing. (The reverse implication is that if you do have to write any one of those, you probably have to write all of them.)

So the question boils down to "which single-responsibility component class can take advantage of move semantics?" The general answer is: Anything that manages a resource. The point is that the move constructor/assigner will just reseat the resource to the new object and invalidate the old one, thus avoiding the (presumed expensive or impossible) new allocation and deep copying of the resource.

The prime example is anything that manages dynamic memory, where the move operation simply copies the pointer and sets the old object's pointer to zero (so the old object's destructor does nothing). Here's a naive example:

class MySpace
{
  void * addr;
  std::size_t len;

public:
  explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }

  ~MySpace() { ::operator delete(addr); }

  MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
  { /* copy memory */ }

  MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
  { rhs.len = 0; rhs.addr = 0; }

  // ditto for assignment
};

The key is that any copy/move constructor will do a full copying of the member variables; it is only when those variables are themselves handles or pointers to resources that you can avoid copying the resource, because of the agreement that a moved object is no longer considered valid and that you're free to steal from it. If there's nothing to steal, then there's no benefit in moving.

抱猫软卧 2024-12-18 11:45:21

对于您编写的类模板,采用移动构造函数没有任何优势。

如果动态分配成员数组会有一个优点。但使用普通数组作为成员时,没有什么可以优化的,您只能复制值。无法移动它们。

For the class template you wrote, there's no advantage to take in a move constructor.

There would be an advantage if the member array was allocated dynamically. But with a plain array as a member, there's nothing to optimize, you can only copy the values. There's no way to move them.

爱的十字路口 2024-12-18 11:45:21

在这种情况下它没有用,因为 int 没有移动构造函数。

但是,如果这些是字符串,它可能会很有用,例如:

template<unsigned int N>
class Foo {
public:
    // [snip]

    Foo(Foo<N>&& other) {
        // move each element from other._nums to _nums
        std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
    }

    // [snip]

private:
    std::string _nums[N];
};

现在您可以避免在移动时复制字符串。如果您完全省略所有复制/移动构造函数,我不确定符合 C++11 的编译器是否会生成等效代码,抱歉。

(换句话说,我不确定 std::move 是否是专门定义来对数组进行按元素移动的。)

In this case it's not useful because int has no move-constructors.

However, it could be useful if those were strings instead, for example:

template<unsigned int N>
class Foo {
public:
    // [snip]

    Foo(Foo<N>&& other) {
        // move each element from other._nums to _nums
        std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
    }

    // [snip]

private:
    std::string _nums[N];
};

Now you avoid copying strings where a move will do. I'm not sure if a conforming C++11 compiler will generate equivalent code if you omit all the copy-/move-constructors completely, sorry.

(In other words, I'm not sure if std::move is specially defined to do an element-wise move for arrays.)

近箐 2024-12-18 11:45:21

通常,当您的类管理资源时,会实现移动语义。由于在您的情况下,该类不管理资源,因此移动语义更像是复制语义,因为没有任何移动

为了更好地理解何时需要 move-semantic,请考虑将 _nums 设为指针,而不是数组:

template<unsigned int N>
class Foo {
public:
    Foo() 
    {
        _nums = new int[N](); //allocate and zeo-initialized
    }
    Foo(const Foo<N>& other) 
    {
        _nums = new int[N];
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) 
    {
         _nums = other._nums; //move the resource
         other._nums=0; //make it null
    }

    Foo<N> operator=(const Foo<N> & other); //implement it!

    virtual ~Foo() { delete [] _nums; }

private:
    int *_nums;
};

Usually, move-semantic is implemented when your class manages resource. Since in your case, the class doesn't manages resource, the move-semantic would be more like copy-semantic, as there is nothing to be moved.

To better understand when move-semantic becomes necessary, consider making _nums a pointer, instead of an array:

template<unsigned int N>
class Foo {
public:
    Foo() 
    {
        _nums = new int[N](); //allocate and zeo-initialized
    }
    Foo(const Foo<N>& other) 
    {
        _nums = new int[N];
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) 
    {
         _nums = other._nums; //move the resource
         other._nums=0; //make it null
    }

    Foo<N> operator=(const Foo<N> & other); //implement it!

    virtual ~Foo() { delete [] _nums; }

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