Search code examples
c++goswapunique-ptr

Swapping pointers: equivalent of `std::unique_ptr::swap`


I'm not sure how to translate C++ std::unique_ptr::swap into Go.

I am using std::unique_ptr<T,Deleter>::swap in C++:

std::unique_ptr<bool[]> maskA{new bool[num]};
std::unique_ptr<bool[]> maskB{new bool[num]};

// ...

maskA.swap(maskB); // Swap operation

Would I be missing something if I just use a simple maskA, maskB = maskB, maskA in Go?

maskA := make([]bool, num)
maskB := make([]bool, num)

// ...

maskA, maskB = maskB, maskA

Does this swap have the same functionality?


Solution

  • The short answer:

    This will be equivalent in terms of functionality but the actual performance implications are different. Use *[]bool for nearest equivalent.

    Properties:

    The assignment with exchange has the following properties:

    • the values of maskA and maskB will be exchanged
    • because these are slices, the underlying array of bools is not copied
    • thanks to compiler optimizations, the slice descriptors might not be copied either

    The last two points are important and due to the way slices are built: they are small descriptors pointing to the underlying array. See slice internals for more details.

    Differences between C++ and Go:

    In C++, a unique_ptr<foo> always holds a *foo and the swap just involves exchanging addresses, this has predictable performance implications.

    In Go, the type is whatever you give it, and the compiler will copy values. The amount of work depends on the type:

    • If these are big (eg: arrays: [1000]bool) it could get expensive
    • If they are slices as you have now (eg: []bool), the slice descriptor will be copied.
    • If they are pointers (eg: *[]bool), this will be the same as C++

    So to be guaranteed equivalency to the C++ code, you should be using a pointer to a bool slice (*[]bool) as your Go types.

    Go optimizations:

    The Go compiler will try to optimize away the variable swap.

    Here is a trivial example:

    func main() {
      a := make([]bool, 0)
      b := make([]bool, 0)
      fmt.Println(a, b)
      a, b = b, a
      fmt.Println(a, b)
    }
    

    We can dump the optimization steps during compilation using GOSSAFUNC=main go build .

    Looking at the resulting ssa.html file, we can see that the a, b = b, a line is dropped entirely and the following code is changed to something like fmt.Println(b, a).

    When this happens your swap becomes just coding nicety and has no performance implications, but this will not always be the case (compiler optimizations are very specialized, there are cases where it cannot do this. eg: swapping two struct field members cannot be optimized away).

    Unequal comparison:

    Comparing the two is a little bit strange. A closer equivalent in C++ would be two plain variables to be swapped (whether pointers or not). The main goal of a unique_ptr is to simplify object ownership and delete the underlying object when the unique_ptr goes out of scope.

    Go has no equivalent to unique_ptr mostly because it does not need to, all memory is garbage collected.