【问题标题】:Reduce Copy Constructor Calls减少复制构造函数调用
【发布时间】:2018-08-27 16:28:28
【问题描述】:

以下代码是我正在处理的项目中的一个最小示例。主要问题是我想减少对复制构造函数的调用次数,但我不清楚这样做的正确方法。

#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的结果。

主要问题:在我的应用程序中,复制构造函数很昂贵(它涉及内存分配)。另一方面,转让相对便宜。减少对复制构造函数的调用的正确方法是什么?

我考虑了一些解决方案,但没有一个能让我满意:

  • 据我了解,从阅读中,operator+ 不应该有第一个参数的引用,因为这有助于链接临时对象。因此,这个拷贝构造函数似乎是不可避免的。

  • 以下代码明显更快(由于没有复制构造函数调用):c = a; c += b; 我可以使用这种格式编写代码,但这需要更精细的方法。我希望编译器比我自己进行这些调整更聪明。

  • 我可以实现一个函数add(MyClass &amp;,const MyClass &amp;,const MyClass &amp;);,但这失去了使用加法运算符的便利性(并且由于我使用的不同数据类型的数量而需要大量(无意识的)编码)。

  • 我查看了这些问题,但在这种情况下我没有看到任何可以提高性能的建议:

Copy constructor called twiceCopy constructor called twiceConditions for copy elision

对 cme​​ts 的回应:

  • 私有数据包括 MPFR 和 MPFI,构造函数包括该数据的初始化。也许构造函数的不同实现是合适的,但我不确定。

  • 我考虑了一个移动构造函数,但有时我也想要一个复制分配。从cppreference 看来,这些不能共存(或者至少在我最初尝试时出现错误)。看来这应该是最好的选择了。

【问题讨论】:

  • 为什么不将(常量)引用传递给operator+ 函数?
  • “据我了解,从阅读中,operator+ 不应该有第一个参数的引用” - 你从哪里读到的?
  • 并根据this canonical implementation reference binary operators 应该相互实现。就像使用operator+= 来实现operator+。在那种的情况下,你应该按值传递第一个参数。
  • 有足够的信息建议您定义一个移动构造函数,并且您字面上不会有复制构造函数调用。
  • 我同意这个评价。 move-ctor 和 move-assignment 都可以使用。 See it live。坦率地说,如果您的设计允许(很可能),我会同时实现它们

标签: c++ copy-constructor rvo


【解决方案1】:

为了尽量减少复制构造函数调用,我建议您定义移动构造函数并完美转发您的运算符参数。移动构造函数:

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,您可以保存复制构造函数。

假设您有一个返回 MyClass 实例的函数:

MyClass something() {return MyClass();}

如果添加函数返回值并立即存储,例如:

MyClass c=something()+something();

那么就不会涉及复制构造函数了。

我已经放置了一系列示例here,其中我将const MyClass&amp; 参数与operator+ 一起使用,并将完美转发参数与operator- 一起使用。您可以看到它在最后一个示例中有所不同,但在所有其他示例中都没有。这就是为什么我说“有正确的电话”。如果您必须操作可以像这样转发的对象,那可能值得一试。

【讨论】:

    【解决方案2】:

    您正在通过副本传递lhs。这就是为什么你有额外的复制构造函数调用。修改你的operator+:

    MyClass operator+(const MyClass &amp;lhs, const MyClass &amp;rhs)

    【讨论】:

    • 如果我这样做,那么我需要在 operator+ 中创建一个新的 MyClass 对象来存储总和,因为我不想更改 lhs 和 rhs。这不会减少构造函数调用的次数。
    猜你喜欢
    • 2021-05-14
    • 2018-10-17
    • 1970-01-01
    • 2017-02-08
    • 2011-07-17
    • 1970-01-01
    • 2013-04-17
    • 2012-02-28
    相关资源
    最近更新 更多