How to use noexcept in assignment operator with copy-and-swap idiom?
Asked Answered
L

2

19

The move assignment operator should often be declared noexcept (i.e. to store the type in STL containers). But the copy-and-swap idiom allows both copy- and move- assignment operators to be defined in a single piece of code. What to do with noexcept specifier in this case? The copy construction can throw, but I doubt whether it can violate the noexcept specifier.

// Is it correct considering that T copy constructor can throw?
T& operator=(T other) noexcept;
Laquitalar answered 17/9, 2013 at 11:7 Comment(2)
The noexcept here is fine. Note that the is_nothrow_*_constructible traits will still return the correct value because they're implemented similar to noexcept(declval<T&>() = declval<T const&>()) which also takes into account whether the copy or move constructor throws (because that copy/move needs to happen to pass by value).Flour
You may also be interested in: https://mcmap.net/q/15928/-c-unified-assignment-operator-move-semantics and https://mcmap.net/q/15929/-when-is-overloading-pass-by-reference-l-value-and-r-value-preferred-to-pass-by-valueStereotyped
D
13

Since the copy is made on the caller's side of the call, it is not part of what your function does. It can therefore not be controlled by your function and consequently, you can not include this information in the noexcept specification.

The only thing you could do is to play it safe and add both options to your noexcept specification. Of course, that means you are getting some false-negatives.

Deodorant answered 17/9, 2013 at 11:19 Comment(3)
Do you mean that exception thrown in copy constructor while passing py value to a function is not a part of this function, so it can be safely declared as noexcept?Laquitalar
@Laquitalar "Safely" is a strong word. Basically, I'd say yes to that. The problem is that the caller must be aware of what it requires for his own noexcept specification. But then, from your perspective it's a SEP (somebody else's problem).Deodorant
I'd answer that with "yes it is safe" as in "is correct" IFF the body does indeed not throw (which is the case for a swap). Example code proving this: godbolt.org/z/bzLOGA. Note how the traits is_nothrow_* still return the correct values.Godchild
S
10

As usual, Daniel Frey is correct. All I want is showing a piece of code that illustrates the point.

#include <iostream>

struct foo {

    foo() = default;

    foo(const foo&) {
        std::cout << "throw\n";
        throw 1;
    }

    foo& operator =(foo) noexcept {
        return *this;
    }
};

int main() {

    foo f, g;
    try {
        f = g; // throws
    }
    catch(int) {
        std::cout << "catch\n";
    }
}

When compiled with gcc 4.8.1 (-std=c++11 -Wall -Wextra -pedantic) it gives no warnings. Running the code produces the following output:

throw
catch

Therefore, the copy constructor does throw when called but that's no considered inside operator =() and, therefore, the noexcept promise was fulfilled. Otherwise, terminate would be called before catch could be printed out.

Supple answered 17/9, 2013 at 15:6 Comment(1)
May I suggest to revise the code to this and include a link: godbolt.org/z/bzLOGAGodchild

© 2022 - 2024 — McMap. All rights reserved.