当编写自己的 swap() 函数/方法具有性能优势时

when there is performance benefit for writing own swap() function / method

本文关键字:方法 性能 函数 自己的 swap      更新时间:2023-10-16

假设我有这样的类:

struct A{
  std::string a;
  std::string b;
  std::string c;
  std::string d;
};

如果我使用 std::swap ,它可能会做这样的事情:

// pseudo-code:
void std::swap(A &a, A &b){
   A tmp = std::move(a);
   a = std::move(b);
   b = std::move(tmp);
}

它将使用默认的 c-tor tmp构造"空"对象 - 通常是廉价的操作。然后它希望移动 3 次,除非在疯狂的情况下移动衰减以复制。

但是,如果我自己交换:

void swap(A &a, A &b){
   std::swap(a.a, b.a);
   std::swap(a.b, b.b);
   std::swap(a.c, b.c);
   std::swap(a.d, b.d);
}

它肯定会使用更少的内存,但它仍然需要构造空std::string - 4 倍!!

我可以狂野,用单std::string做到.

在所有情况下,它看起来都不像很大的改进。

我能想到的唯一适当情况是默认 c-tor 是否贵得离谱。我说的对吗?

好吧,

你不能说你需要,或者永远不应该,制作自己的swap,这完全取决于上下文。在您的示例中,这很可能是不必要的,但您的类的逻辑可能更复杂。

假设你有一个类,它保存着指向某个数据结构的指针,并且有很多与该结构关联的指标,每个指标都需要很长时间来计算,并且需要大量的空间来存储,并且这些指标在对数据进行一些计算时用作临时指标(我知道以下示例可能是一个蹩脚设计的类, 但它应该说明这个想法(:

class MyClass
{
public:
   void doSomeWork()
   {
       //uses _metricsOneValue and _metricsTwoValue as temporaries,
       //calls other functions that use them as temporaries too, etc.
   }
private:
   //used only in doSomeWork and functions called by it.
   //Value overwritten each call.
   SomeBigClass _metricsOneValue; 
   SomeBigClass _metricsTwoValue;
   <...>
   SomeOtherClass * _data;
}

现在假设您需要swap()此类的两个实例。最直接的实现将复制所有内容,包括旧指标,实际上,目前不需要这些指标,因为下次您调用doSomeWork()时它们将被覆盖。因此,在这种情况下,您只需将指针交换到数据结构即可优化swap

我希望一个理智的std::swap(std::string&, std::string&)实现可以在没有任何临时的情况下进行交换。它应该只是能够交换指针,尽管我承认小字符串优化可能会在某些实现的特定工作中抛出扳手。

您可以随时使用 std::string::swap 成员函数,我希望更多地利用这种令人愉快的"优化"。我的意思是,否则,它有什么意义?:)

void swap(A &a, A &b)
{
   a.a.swap(b.a);
   a.b.swap(b.b);
   a.c.swap(b.c);
   a.d.swap(b.d);
}

无论如何,您都需要实现它,即使它只是您的 move 构造函数的一部分,否则默认的 std::swap(A&, A&) 实现无法做任何事情。

总之,是的,您应该编写自己的swap函数,是的,您应该使用它来调用标准交换功能。因为两者都需要,所以没有性能比较。

实现

自己的 swap 1 函数有一个好处的一种情况是在赋值运算符中使用复制和交换习惯用法来管理/传输动态分配的内存。

如果您有以下类:

#include <algorithm> // std::copy
#include <cstddef> // std::size_t
class MyArray{
public:
    // (default) constructor
   MyArray(std::size_t size = 0) 
       : mSize(size), mArray(mSize ? new int[mSize]() : 0)
   { }
   // copy-constructor
   MyArray(const MyArray& other) 
       : mSize(other.mSize), mArray(mSize ? new int[mSize] : 0),
   { std::copy(other.mArray, other.mArray + mSize, mArray); }
   // destructor
   ~MyArray(){ delete [] mArray; }
private:
    std::size_t mSize;
    int* mArray;
};

1. 实现赋值运算符

而不是:

MyArray& operator=(const MyArray& other){
   if (this != &other){
    // get the new data ready before we replace the old
    std::size_t newSize = other.mSize;
    int* newArray = newSize ? new int[newSize]() : 0;  
    std::copy(other.mArray, other.mArray + newSize, newArray);  
    // replace the old data  
    delete [] mArray;
    mSize = newSize;
    mArray = newArray;
}
return *this;
} 

你可以做:

MyArray& operator=(MyArray other){
    swap(*this, other);  
    return *this;
} 

2. 要安全地交换类的成员:

friend void swap(MyArray& first, MyArray& second){
    using std::swap; 
    // by swapping the members of two classes,
    // the two classes are effectively swapped
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

注:本答中的见解借用于此。


1 交换函数是一个非抛出函数,它交换类的两个对象,成员到成员。我们可能会想使用std::swap而不是提供我们自己的,但这是不可能的; std::swap在其实现中使用了复制构造函数和复制赋值运算符,我们最终会尝试根据自身来定义赋值运算符!