我可以在C++ 98中使用复制构造函数和赋值运算符来模拟移动构造函数和移动赋值运算符功能以提高性能吗,只要我知道复制构造函数和复制赋值将仅针对代码中的临时对象调用,或者我在我的身上插入needle眼睛?
我已经举了两个例子,一个是普通的复制构造函数和复制赋值运算符,另一个是模拟移动构造函数和移动赋值运算符并在 vector 中插入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
我们可以观察到性能会有所提高。
运算符(operator)在c++ 98中模拟了功能,即使我确定该拷贝
构造函数和赋值仅在存在临时对象时调用
创建了吗?
和赋值运算符在C++ 98?
最佳答案
您将无法使语言以与C++ 11及更高版本相同的方式理解R值,但是您仍然可以通过创建自定义“R值”类型来模拟所有权转移来近似move
语义的行为。 。
该方法
“移动语义”实际上只是以惯用的方式破坏性地编辑/窃取从引用到对象的内容。这与从不可变 View 复制到对象相反。在C++ 11及更高版本中在语言级别引入的惯用方法是作为重载集合提供给我们的,它使用l值表示拷贝(const T&
),使用(可变)r值表示移动(T&&
)。
尽管该语言为使用r值引用处理生命周期的方式提供了更深层次的钩子(Hook),但是我们可以通过创建类似rvalue
的类型来绝对模拟C++ 98中的移动语义,但是它将有一些局限性。我们需要的是一种创建重载集的方法,该重载集可以消除复制概念和移动概念之间的歧义。
过载集对于C++而言并不是什么新鲜事物,而这可以通过精简包装类型来实现,该类型可以使用基于标签的分派(dispatch)来消除过载。
例如:
// 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
这是working example on compiler explorer,用于比较复制程序集和移动程序集。
局限性
没有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不同,将没有自动生成的移动构造函数或赋值运算符-因此,这对于要向其添加“移动”支持的类型来说是一种手动操作。
值得指出的是,在某些情况下,复制构造函数和赋值运算符是免费提供的,而移动则是手动操作。
rvalue
不是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?
这可能会导致代码中的错误行为,并且不容易看到。使用包装器方法至少可以使这种破坏更加明显
关于c++ - 在c++ 98中实现move构造函数和move赋值运算符,以实现更好的性能,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34607800/