减少复制构造函数调用
Reduce Copy Constructor Calls
以下代码是我正在处理的项目中的一个最小示例。主要问题是我想减少对复制构造函数的调用次数,但我不清楚这样做的正确方法。
#include<iostream>
class MyClass
{
public:
MyClass() {std::cout << "Default Constructor\n";}
MyClass(const MyClass &input) {std::cout << "Copy Constructor\n";}
MyClass & operator=(const MyClass &input)
{std::cout << "Assignment\n"; return *this;}
MyClass & operator+=(const MyClass &input) {return *this;}
friend MyClass operator+(MyClass lhs,const MyClass &);
};
MyClass operator+(MyClass lhs,const MyClass &rhs)
{lhs+=rhs;return lhs;}
int main()
{
MyClass a,b,c;
c=a+b;
return 0;
}
当我运行代码时,输出是:
Default Constructor
Default Constructor
Default Constructor
Copy Constructor
Copy Constructor
Assignment
在a、b、c的构造中调用了三个默认构造函数。
operator+ 中的第一个参数调用了两个复制构造函数,operator+ 中调用了 return。
赋值是将a+b赋值给c的结果
主要问题:在我的应用程序中,复制构造函数是昂贵的(它涉及内存分配)。另一方面,分配相对便宜。减少对复制构造函数的调用的正确方法是什么?
我考虑了一些解决方案,但是none让我很开心:
据我所知,从阅读中可以看出,operator+ 不应该对第一个参数有引用,因为这有助于链接临时变量。因此,这个拷贝构造函数似乎是不可避免的。
以下代码明显更快(由于没有复制构造函数调用):c = a; c += b;
我可以使用这种格式编写代码,但这需要更精细的方法。我希望编译器更智能,而不是让我自己进行这些调整。
我可以实现一个函数 add(MyClass &,const MyClass &,const MyClass &);
但这失去了使用加法运算符的便利性(并且由于我要处理的不同数据类型的数量,需要大量(无意识的)编码我正在使用)。
我查看了这些问题,但我没有看到任何可能在这种情况下提高性能的建议:
Copy constructor called twice, Copy constructor called twice, and Conditions for copy elision
对评论的回复:
私有数据包括 MPFR 和 MPFI,构造函数包括对这些数据的初始化。也许构造函数的不同实现是合适的,但我不确定。
我考虑过移动构造函数,但有时我也想要复制复制赋值。从 cppreference 看来,它们不能共存(或者至少我一开始尝试时出错)。看来这应该是最好的选择了。
您正在通过副本 lhs
。这就是为什么你有额外的复制构造函数调用。修改你的 operator+
:
MyClass operator+(const MyClass &lhs, const MyClass &rhs)
为了尽量减少复制构造函数调用,我建议您定义移动构造函数并完美转发运算符参数。移动构造函数:
MyClass(MyClass &&input) {std::cout << "Move Constructor\n";}
完美转发运算符:
template<typename T>
friend MyClass operator+(T &&lhs,T &&rhs) {return std::forward<T>(lhs);}
通过正确的调用,您的运算符将涉及移动构造函数而不是复制构造函数。例如,如果您添加来自函数的对象并立即存储结果(例如 MyClass c=a+b;
而不是 MyClass c;c=a+b;
),感谢 RVO 您可以保存复制构造函数。
假设您有一个 return 是 MyClass
实例的函数:
MyClass something() {return MyClass();}
如果添加函数 return 值并立即存储它们,例如:
MyClass c=something()+something();
那么将不会涉及复制构造函数。
我提供了一系列示例 here,其中我将 const MyClass&
参数与 operator+
一起使用,将完美转发参数与 operator-
一起使用。您可以看到它在最后一个示例中有所不同,但在所有其他示例中都没有。这就是为什么我说 "with the right calls"。如果你必须像那样操作可以转发的对象,那可能值得一试。
以下代码是我正在处理的项目中的一个最小示例。主要问题是我想减少对复制构造函数的调用次数,但我不清楚这样做的正确方法。
#include<iostream>
class MyClass
{
public:
MyClass() {std::cout << "Default Constructor\n";}
MyClass(const MyClass &input) {std::cout << "Copy Constructor\n";}
MyClass & operator=(const MyClass &input)
{std::cout << "Assignment\n"; return *this;}
MyClass & operator+=(const MyClass &input) {return *this;}
friend MyClass operator+(MyClass lhs,const MyClass &);
};
MyClass operator+(MyClass lhs,const MyClass &rhs)
{lhs+=rhs;return lhs;}
int main()
{
MyClass a,b,c;
c=a+b;
return 0;
}
当我运行代码时,输出是:
Default Constructor
Default Constructor
Default Constructor
Copy Constructor
Copy Constructor
Assignment
在a、b、c的构造中调用了三个默认构造函数。
operator+ 中的第一个参数调用了两个复制构造函数,operator+ 中调用了 return。
赋值是将a+b赋值给c的结果
主要问题:在我的应用程序中,复制构造函数是昂贵的(它涉及内存分配)。另一方面,分配相对便宜。减少对复制构造函数的调用的正确方法是什么?
我考虑了一些解决方案,但是none让我很开心:
据我所知,从阅读中可以看出,operator+ 不应该对第一个参数有引用,因为这有助于链接临时变量。因此,这个拷贝构造函数似乎是不可避免的。
以下代码明显更快(由于没有复制构造函数调用):
c = a; c += b;
我可以使用这种格式编写代码,但这需要更精细的方法。我希望编译器更智能,而不是让我自己进行这些调整。我可以实现一个函数
add(MyClass &,const MyClass &,const MyClass &);
但这失去了使用加法运算符的便利性(并且由于我要处理的不同数据类型的数量,需要大量(无意识的)编码我正在使用)。我查看了这些问题,但我没有看到任何可能在这种情况下提高性能的建议:
Copy constructor called twice, Copy constructor called twice, and Conditions for copy elision
对评论的回复:
私有数据包括 MPFR 和 MPFI,构造函数包括对这些数据的初始化。也许构造函数的不同实现是合适的,但我不确定。
我考虑过移动构造函数,但有时我也想要复制复制赋值。从 cppreference 看来,它们不能共存(或者至少我一开始尝试时出错)。看来这应该是最好的选择了。
您正在通过副本 lhs
。这就是为什么你有额外的复制构造函数调用。修改你的 operator+
:
MyClass operator+(const MyClass &lhs, const MyClass &rhs)
为了尽量减少复制构造函数调用,我建议您定义移动构造函数并完美转发运算符参数。移动构造函数:
MyClass(MyClass &&input) {std::cout << "Move Constructor\n";}
完美转发运算符:
template<typename T>
friend MyClass operator+(T &&lhs,T &&rhs) {return std::forward<T>(lhs);}
通过正确的调用,您的运算符将涉及移动构造函数而不是复制构造函数。例如,如果您添加来自函数的对象并立即存储结果(例如 MyClass c=a+b;
而不是 MyClass c;c=a+b;
),感谢 RVO 您可以保存复制构造函数。
假设您有一个 return 是 MyClass
实例的函数:
MyClass something() {return MyClass();}
如果添加函数 return 值并立即存储它们,例如:
MyClass c=something()+something();
那么将不会涉及复制构造函数。
我提供了一系列示例 here,其中我将 const MyClass&
参数与 operator+
一起使用,将完美转发参数与 operator-
一起使用。您可以看到它在最后一个示例中有所不同,但在所有其他示例中都没有。这就是为什么我说 "with the right calls"。如果你必须像那样操作可以转发的对象,那可能值得一试。