在c ++ 98中实现move构造函数和move赋值运算符,以实现更好的性能

阿杰亚达夫

我可以在C ++ 98中使用复制构造函数和赋值运算符来模拟移动构造函数和移动赋值运算符功能以提高性能吗,只要我知道复制构造函数和复制赋值将仅针对代码中的临时对象调用,或者我在我的身上插入needle眼睛?

我已经举了两个例子,一个是普通的复制构造函数和复制赋值运算符,另一个是模拟移动构造函数和移动赋值运算符并在向量中推送10000个元素以调用复制构造函数。

普通副本构造函数和副本分配运算符的示例(copy.cpp)

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

class MemoryBlock
{
public:

   // Simple constructor that initializes the resource.
   explicit MemoryBlock(int length)
      : _length(length)
      , _data(new int[length])
   {
   }

   // Destructor.
   ~MemoryBlock()
   {

      if (_data != NULL)
      {
         // Delete the resource.
         delete[] _data;
      }
   }


//copy constructor.
MemoryBlock(const MemoryBlock& other): _length(other._length)
      , _data(new int[other._length])
{

      std::copy(other._data, other._data + _length, _data);
}

// copy assignment operator.
MemoryBlock& operator=(MemoryBlock& other)
{
  //implementation of copy assignment
}

private:
   int  _length; // The length of the resource.
   int*  _data; // The resource.
};


int main()
{
   // Create a vector object and add a few elements to it.
   vector<MemoryBlock> v;
   for(int i=0; i<10000;i++)
   v.push_back(MemoryBlock(i));

   // Insert a new element into the second position of the vector.
}

带有复制构造函数和复制赋值运算符的模拟移动构造函数和移动赋值运算符功能的示例(move.cpp)

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

class MemoryBlock
{
public:

   // Simple constructor that initializes the resource.
   explicit MemoryBlock(int length=0)
      : _length(length)
      , _data(new int[length])
   {
   }

   // Destructor.
   ~MemoryBlock()
   {

      if (_data != NULL)
      {
         // Delete the resource.
         delete[] _data;
      }
   }


// Move constructor.
MemoryBlock(const MemoryBlock& other)
{
   // Copy the data pointer and its length from the 
   // source object.
   _data = other._data;
   _length = other._length;
   // Release the data pointer from the source object so that
   // the destructor does not free the memory multiple times.
   (const_cast<MemoryBlock&>(other))._data  = NULL;
    //other._data=NULL;
}

// Move assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
   //Implementation of move constructor
   return *this;
}

private:
   int  _length; // The length of the resource.
   int*  _data; // The resource.
};


int main()
{
   // Create a vector object and add a few elements to it.
   vector<MemoryBlock> v;
   for(int i=0; i<10000;i++)
   v.push_back(MemoryBlock(i));

   // Insert a new element into the second position of the vector.
}

我观察到性能有所提高,但需要付出一些代价:

$ g++ copy.cpp -o copy
$ time ./copy 
real    0m0.155s
user    0m0.069s
sys 0m0.085s

$ g++ move.cpp -o move
$ time ./move 
real    0m0.023s
user    0m0.013s
sys 0m0.009s

我们可以观察到性能会有所提高。

  • 在c ++ 98中实现移动构造函数和移动赋值运算符模拟的功能有什么陷阱,即使我确定仅在创建临时对象时才调用复制构造函数和赋值?
  • 还有其他方法/技术可以在c ++ 98中实现move构造函数和赋值运算符吗?
人工编译器

您将无法使语言以与C ++ 11及更高版本相同的方式理解R值,但是您仍然可以move通过创建自定义“ R值”类型来模拟所有权转移来近似语义行为

该方法

“移动语义”实际上只是以惯用的形式破坏性地编辑/窃取从引用到对象的内容。这与从不可变视图复制到对象相反。在C ++ 11及更高版本中在语言级别引入的惯用方法是作为重载集提供给我们的,它使用l值表示副本(const T&),使用(可变)r值表示移动(T&&)。

尽管该语言为使用r值引用处理生命周期的方式提供了更深层次的钩子,但是我们可以通过创建-like类型来绝对地模拟C ++ 98中的移动语义rvalue,但是它将有一些限制。我们需要的是一种创建重载集的方法,该重载集可以消除复制概念与移动概念之间的歧义。

过载集对于C ++而言并不是什么新鲜事,这可以通过精简包装类型来实现,该类型可以使用基于标记的分派来消除过载。

例如:


// A type that pretends to be an r-value reference
template <typename T>
class rvalue { 
public:
    explicit rvalue(T& ref) 
        : _ref(&ref)
    {

    }

    T& get() const {
        return *_ref;
    }

    operator T&() const {
        return *_ref;
    }

private:
    T* _ref; 
};

// returns something that pretends to be an R-value reference
template <typename T>
rvalue<T> move(T& v)
{
    return rvalue<T>(v);
}

通过操作员访问成员,我们将无法表现出完全类似于引用的行为.,因为该功能在C ++中不存在-因此必须get()获取引用。但是我们可以发出一种信号,该信号在代码库中变得很惯用,以破坏性地更改类型。

rvalue根据您的需求,类型也可以更具创意-为了简洁起见,我将其简化。operator->至少值得增加一种直接访问成员的方法。

我没有进行T&&->const T&&转换,而是T&&要进行U&&转换(这里U是的基数T),而T&&参考折叠到T&可以通过rvalue使用隐式转换运算符/构造函数进行修改来引入这些内容(但可能需要一些light-SFINAE)。但是,我发现在通用编程之外很少需要这样做。对于纯/基本的“移动语义学”,这已经足够了。

整合在一起

集成此“ rvalue”类型就像为该类型“移出”的rvalue<T>位置添加重载一样简单T在上面的示例中,只需要添加构造函数/移动赋值运算符:

    // Move constructor.
    MemoryBlock(rvalue<MemoryBlock> other)
        : _length(other.get()._length),
          _data(other.get()._data)
    {
        other.get()._data = NULL;
    }

    MoveBlock& operator=(rvalue<MemoryBlock> other)
    {
        // same idea
    }

这使您可以保持复制构造函数的惯用性,并模拟“移动”构造函数。

现在的用法可以变成:

MemoryBlock mb(42);

MemoryBlock other = move(mb); // 'move' constructor -- no copy is performed

这是编译器资源管理器上的一个有效示例,用于比较复制程序集和移动程序集。

局限性

没有PR值可rvalue转换

这种方法的一个显着局限性是,您无法进行C ++ 11或更高版本中PR值到R值的转换,例如:

MemoryBlock makeMemoryBlock(); // Produces a 'PR-value'

...

// Would be a move in C++11 (if not elided), but would be a copy here
MemoryBlock other = makeMemoryBlock(); 

据我所知,如果没有语言支持,这是无法复制的。

没有自动生成的移动构造器/分配

与C ++ 11不同,将没有自动生成的移动构造函数或赋值运算符-因此,这对于要向其添加“移动”支持的类型来说是一种手动操作。

值得指出的是,在某些情况下,复制构造函数和赋值运算符是免费提供的,而移动则是手动操作。

Anrvalue不是L值参考

在C ++ 11中,命名的R值引用l值引用。这就是为什么您看到类似以下代码的原因:

void accept(T&& x)
{
    pass_to_something_else(std::move(x));
}

如果没有编译器支持,则无法对这种名为r值到l值的转换进行建模。这意味着rvalue引用将始终像R值引用一样工作。例如:

void accept(rvalue<T> x)
{
    pass_to_something_else(x); // still behaves like a 'move'
}

结论

简而言之,您将无法完全支持PR值之类的语言。但是,您至少可以实现一种允许通过“尽力而为”的尝试将内容从一种类型有效地移动到另一种类型的方法。如果在代码库中一致采用此方法,则它可能与C ++ 11及更高版本中的适当移动语义一样惯用。

我认为,尽管有上述限制,这种“尽力而为”还是值得的,因为您仍然可以以惯用的方式更有效地转让所有权。


注意:建议同时重载T&const T&尝试“移动语义”。这里的大问题是,它可能会因简单的代码而无意间变成破坏性的代码,例如:

SomeType x; // not-const!
SomeType y = x; // x was moved?

这可能导致代码中的错误行为,并且不容易看到。使用包装器方法至少可以使这种破坏更加明显

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

C ++复制/移动构造函数和赋值运算符

C ++中的赋值运算符模板和复制构造函数

C++中的复制构造函数和赋值运算符

C ++ 20中的运算符==和<=>应该实现为成员还是自由函数?

删除默认C ++复制和移动构造函数和赋值运算符的缺点?

复制构造函数,赋值运算符C ++

C ++:“ T a = b”-复制构造函数或赋值运算符?

C中sizeof运算符的实现

根据move构造函数实现复制分配运算符

在 C++ 中使用带有字符串的复制构造函数和/或赋值运算符时堆栈溢出

C ++向量复制构造函数和赋值运算符是否还会复制保留空间?

C ++赋值运算符和重载

如何在临时容器类中实现复制构造函数和赋值运算符?

C ++构造函数和运算符问题

C ++链表-构造函数和运算符重载

C ++:运算符new和default构造函数

如何在C#中实现包含运算符

在C ++中为Shell实现bash运算符

C ++ ==运算符重载(实现)

C ++-没有复制构造函数或赋值运算符的数组操作

C ++复制构造函数和=运算符中的重载情况

复制构造函数和Assignemnt运算符重载在C ++中不起作用

未实现赋值运算符时调用构造函数

链表:如何实现析构函数、复制构造函数和复制赋值运算符?

为什么在C ++ 11或C ++ 14中,当我声明移动赋值运算符时,编译器会隐式删除副本构造函数?

为什么要使用删除的move构造函数和赋值运算符移动对象?

我应该如何为矩阵类实现复制构造函数和赋值运算符?

C/C++ 中的逻辑否定和赋值运算符?

+ =和= + C赋值运算符有什么区别