[英]How to reduce the + operator memory consumption for self-defined class in C++?
我举几个例子来说明我的问题:
class BigClass
{
public:
static int destruct_num;
friend BigClass operator + (const BigClass &obj1, const BigClass &obj2);
std::vector<int> abc;
BigClass()
{
}
~BigClass()
{
destruct_num++;
std::cout << "BigClass destructor " <<destruct_num<< std::endl;
}
BigClass(BigClass&& bc) :abc(std::move(bc.abc))
{
std::cout << "move operation is involed" << std::endl;
}
};
int BigClass::destruct_num = 0;
BigClass operator + (const BigClass &obj1, const BigClass &obj2)
{
BigClass temp;
temp.abc = obj1.abc;
temp.abc.insert(temp.abc.end(), obj2.abc.begin(), obj2.abc.end());
return temp;
}
int main(void)
{
BigClass a;
a.abc = { 1,2,3 };
BigClass b;
b.abc = { 5,6,7 };
BigClass c = a + b;
// for (auto& v : c.abc)
// std::cout << v << " ";
return 0;
}
关于operator +
一个问题是我们必须暂时生成一个临时BigClass
对象然后返回它。 有没有办法减轻这种负担?
通常:
[...]编译器是允许的,但不要求省略副本[...]
无论如何,你的函数应该由任何现代编译器优化,因为复制省略 。
这是一个例子:
operator+
的结果在汇编部分:
call operator+(BigClass const&, BigClass const&)
addl $12, %esp
如您所见,为了复制结果,不会调用任何复制构造函数 。
实际上,如果我们在GCC中禁用复制省略优化, 结果会发生变化:
call operator+(BigClass const&, BigClass const&)
addl $12, %esp
subl $8, %esp
leal -20(%ebp), %eax
pushl %eax
leal -56(%ebp), %eax
pushl %eax
call BigClass::BigClass(BigClass&&)
addl $16, %esp
subl $12, %esp
leal -20(%ebp), %eax
pushl %eax
call BigClass::~BigClass()
addl $16, %esp
在调用operator+
,复制(或在这种情况下移动) 构造函数被调用,并在临时对象的析构函数之后。
请注意,即使禁用优化( -O0
)也可以获得复制省略。
使用旧版本获得相同的结果: GCC 4.4.7 。
由于并非所有体系结构都保证了复制省略,因此您可以实现一些不同的解决方案。
一种可能的解决方案是避免在函数内部分配临时变量,要求调用者保留该空间。 为此,您应该使用“自定义”方法并避免使operator+
过载。
void sum_bigClasses(const BigClass& obj1, const BigClass& obj2, BigClass& output) {
// output.resize(obj1.size() + obj2.size());
// std::copy(...);
}
另一种解决方案可能是为sum实现非const运算符。 一个例子:
BigClass& operator+=(const BigClass& rhs) {
// std::copy(rhs.cbegin(), rsh.cend(), std::back_inserter(abc));
return *this;
}
通过这种方式,类接口允许不同的策略:
这里有两个例子。
第一点:
BigClass o1;
BigClass o2;
// Fill o1 and o2;
o1 += o2;
// only 2 object are alive
第二点:
BigClass o1;
BigClass o2;
// Fill o1 and o2;
BigClass o3 = o1; // Costructor from o1
o3 += o2;
// three different object
编辑 :由于函数是NRVO (返回的表达式不是prvalue ),新的标准C ++ 17都不能保证复制省略。
如果运行代码而不是看到只调用了3个析构函数。 这意味着由于RVO(返回值优化),tmp对象的值被移动而不是复制。 编译器不会复制它,因为它认为没有必要。
临时使用不仅浪费内存而且浪费处理时间(计算N个BigClass
实例的总和可能在N中具有二次时间复杂度)。 避免这种情况没有通用的解决方案,因为它取决于您的对象的使用方式。 在这种情况下:
BigClass c = a + b;
编译器已经免费(或需要,C ++ 17)使用复制省略,如banana36所解释,输入是左值,因此它们无法更改而不会引起很大的惊喜。
另一种情况是:
BigClass f();
BigClass g();
BigClass h = f() + g();
在这种情况下, f()
和g()
是rvalues并且复制它们都是浪费。 可以重复使用它们中的至少一个的存储,例如,可以编写额外的operator +
重载以优化左加数是右值的情况:
BigClass operator +(BigClass &&a, const BigClass &b)
{
a.abc.insert(a.abc.end(), b.abc.begin(), b.abc.end());
return std::move(a);
}
这会重复使用a.abc
的存储空间,并且只要容量足够就可以避免复制其内容。 一个好的副作用是,例如,将每个具有10个元素的N个对象相加将具有线性性能,因为在std::vector
的末尾插入恒定数量的元素具有恒定的摊销成本。 但它只有在选择了operator +
的正确重载时才有效,例如std::accumulate
不是这种情况。 以下是您的主要选项概述:
operator +(const BigClass &, const BigClass &)
和operator +=
,并教育用户不小心使用前者的性能影响。 operator +(BigClass &&, const BigClass &)
和operator +(const BigClass &, BigClass &&)
和operator +(BigClass &&, BigClass &&)
添加重载。 请注意,如果两个重载都带有一个右值引用,则绝对还应该使用两个右值引用添加重载,否则f() + g()
将是一个模糊的调用。 另请注意,右手参数是右值参考的重载最适合与std::deque
而不是std::vector
因为它在前插入时具有较小的时间复杂度,但是仅使用双端队列替换向量如果此用例很常见,则有用,因为deque比vector更慢。 operator +=
并处理用户的挫败感(或者,提供效率较低的操作贬低名称,如copyAdd
)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.