3

Suppose i want to program a sink container for move only types. According to the discussion between Meyer and Sutter on http://scottmeyers.blogspot.de/2014/07/should-move-only-types-ever-be-passed.html the sink parameter should be passed by value and then moved into the member. But in terms of exception safety this sounds to me like a bad idea. When the constructor throws an exception, the value of the sink parameter is lost, while when i pass by rvalue reference and the exception happens before the actual move, the caller can still retrieve the value of the sink parameter.

Example below:

#include <memory>
#include <iostream>
#include <stdexcept>

using widget = std::unique_ptr<int>;

struct sink_1 {
    sink_1(widget&& w) {
       throw std::runtime_error("error");
       w_ = std::move(w);
    }
    widget w_;
};

struct sink_2 {
   sink_2(widget w) {
    throw std::runtime_error("error");
    w_ = std::move(w);
  }
  widget w_;
 };

int main(int argc, char *argv[])
{

    auto w1 = std::make_unique<int>(10);
    auto w2 = std::make_unique<int>(20);

    try {
        sink_1 s1(std::move(w1));
     } catch (std::runtime_error &e) {
        std::cout << *w1 << std::endl;
    }

    try {
        sink_2 s2(std::move(w2));
    } catch (std::runtime_error &e) {
        std::cout << *w2 << std::endl; // crashes
    }


   return 0;
}

Is this a valid and good design?

IcePic
  • 293
  • 1
  • 10
  • Just to give the site their well-deserved credits: If you have complete and working code, your question might fit better for [SE Code Review](http://codereview.stackexchange.com/) actually. – πάντα ῥεῖ Feb 28 '16 at 14:46

2 Answers2

4

It is OK to say "Well, I disagree with Scott Meyers."

The two examples are different in what is called "Exception Guarantee".

sink_1 constructor has strong exception guarantee - meaning that if an exception is throws, w will remain unchanged.

sink_2 constructor has weak exception guarantee - meaning that if an exception is thrown - we are guaranteed that nor s2 nor w will leak any resource.

In my opinion, it is the best to stick to the highest exception guarantee possible, and in this case, I would stick to sink_1 example.

It's not that sink_2 is bad, I personally would opt the first version. for example, my program can be a Web server. w might be an incoming HTTP request. using sink_2 will simply close the request in very aggressive manner if an exception is thrown. sink_1 for difference, will allow me to retry again, use different code path and even give me the possibility to send a decent message to the client saying the operation failed.

yano
  • 4,095
  • 3
  • 35
  • 68
David Haim
  • 25,446
  • 3
  • 44
  • 78
0

No, this is not good design.

What you do with w1 may or may not crash, depending on whether the exception is thrown before or after w1 becomes "moved from".

Once you called std::move on w2 and passed it by value to sink2::sink_2, it has been "moved from" (into the function's parameter). Accessing w2 is guaranteed to crash.

You should always treat a call to std::move as an indicator that the values is going away, and not use it in any way (this is why std::unique_ptr is superior to std::auto_ptr: when you pass an auto_ptr by value, it looks like a copy but it behaves like a move. You can't pass unique_ptr by value (it has no copy constructor), and to move from a named unique_ptr, you have to use std::move).

Bulletmagnet
  • 5,665
  • 2
  • 26
  • 56
  • 1
    It is safe to access a moved-from `unique_ptr` if your access has no preconditions. For example testing against `nullptr`, and `get()` have no preconditions. See http://stackoverflow.com/a/7028318/576911 for more details. – Howard Hinnant Feb 28 '16 at 16:50
  • i agree the example program is not exemplary, but the question was about the struct's ctor – yano Jan 29 '21 at 18:01