2

Copy control seems is related to default constructor, copy constructor, copy assignment operator, destructor, etc, times the user written ones and synthesize ones, plus the move copy / assignment.

It sounds quite complex. I found it hard to remember what will be synthesized and when.

Just wonder, if I don't use move in constructor or assignment operator, do I still need care about the difference?

athos
  • 6,120
  • 5
  • 51
  • 95
  • Do you have any explicit resource management needed to do in your classes? – πάντα ῥεῖ Sep 04 '16 at 06:06
  • @πάνταῥεῖ are you hinting that, resource management normally behaves like a unique pointer that the ownership is important, so I shall also pay attention to synthesize ctor/dtor/assign etc.? – athos Sep 04 '16 at 06:20
  • 1
    For the default cases you'll have to do nothing. See [What is The Rule of Three?](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – πάντα ῥεῖ Sep 04 '16 at 06:22
  • Question like `c++ is quite difficult by I don't care - I just wanna write without thinking` – fnc12 Sep 04 '16 at 07:31
  • 2
    I start by deleting copy constructor and copy assignment (which also disables move). Then depending on the usage, I add back move, or copy. Preferably just construction, if necessary assignment as well. If this is a class which is expensive to copy, but copy is needed, only then will I add copy and move. – Martin Bonner supports Monica Sep 04 '16 at 07:35
  • @fnc12 not really.. it's more like -- C++ is difficult, so when could I write without thinking and still be safe? – athos Sep 04 '16 at 07:35
  • @athos more like `I don't wanna use condoms. Will my GF become pregnant?`. Honestly I advice you to read about constructors because it is a must-know feature – fnc12 Sep 04 '16 at 07:37
  • @fnc12 one has limited time. – athos Sep 04 '16 at 07:39

1 Answers1

3

In user code, you almost never need to write destructors and therefore copy/move constructors and assignment operators. This is because you will compose your classes of RAII-enabled objects.

The only time we need to write a destructor is if we are controlling a non-RAII resource, such as a file handle or database connection exported from a c-style or primitive c++ library.

In this case we simply wrap the resource in a unique_ptr (with custom deleter) or shared_ptr (again, with custom deleter) and we're done.

That leaves us with 2 more scenarios:

  1. A base class of a polymorphic interface (which will not be controlled by a shared_ptr) - in which case we must write a virtual destructor and then possibly implement move/copy in terms of defaulted implementations.

  2. A class which is moveable (because it owns a unique_ptr, say?) and we want it to be copyable. Now we are forced to implement the copy operations and default the move operations.

There are a few other corner cases, such as if your class owns a mutex, which is non-copyable. But if your class owns a mutex it's probably already a design error to require it to be copyable. In any case, by this time you ought to have learned the copy/assignment/move rules off by heart.

some examples:

struct owns_a_file_movable
{
    struct file_deleter {
        void operator()(FILE* f) const noexcept {
            fclose(f);
        }
    };

    // RAII class now, moveable. Copy would not make sense.
    std::unique_ptr<FILE, file_deleter> file_;
};

struct owns_a_file_copyable
{
    struct file_deleter {
        void operator()(FILE* f) const noexcept {
            fclose(f);
        }
    };

    // construct from string
    owns_a_file_copyable(std::string fname)
    : path_(std::move(fname))
    , file_(fopen(path_, "r"), file_deleter())
    {
    }

    // we want it to be copyable. In our case, a copy will open another
    // instance of the file. so we must store the filename.

    owns_a_file_copyable(owns_a_file_copyable const& r)
    : path_(t.path_)
    , file_(fopen(path_, "r"), file_deleter())
    {}

    owns_a_file_copyable& operator=(owns_a_file_copyable const& r)
    {
        auto tmp = r;
        std::swap(path_, tmp.path_);   // changed: was r.path_ which was wrong
        std::swap(file_, tmp.file_);
        return *this;
    }

    owns_a_file_copyable(owns_a_file_copyable&&) = default;
    owns_a_file_copyable& operator=(owns_a_file_copyable&&) = default;


    // std::string is fully RAII
    std::string path_;

    // RAII class now, moveable
    std::unique_ptr<FILE, file_deleter> file_;
};

struct how_most_classes_should_be 
{
    // no destructors, copy operators, assignment or move - it's all 
    // generated for you.

    std::string this_;
    std::string that_;
    std::shared_ptr<const OtherThing> shared_other_; // note - shared semantics
    std::function<void()> closure_;   // these are copyable too
};

For athos

What triggers what?

// ordinary constructor
auto y = owns_a_file_copyable("/users/rhodges/foo.txt");

// copy constructor: owns_a_file_copyable(owns_a_file_copyable const&)
auto x = y;

// copy assignment: owns_a_file_copyable& operator-(owns_a_file_copyable const&)
x = y

// move-constructor: owns_a_file_copyable(owns_a_file_copyable &&)
auto z = std::move(x);

// move-assignment: owns_a_file_copyable& operator-(owns_a_file_copyable &&)
z = std::move(y);

// also move-constructor
extern owns_a_file_copyable make_file();
auto w = make_file();

// this time move-assignment
w = make_file();
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • @athos in the copy assignment operator, a copy is taken of the original file. This copy is now in a temporary variable. The temporary is then swapped with *this. So now *this is the copy and the temporary is what *this used to be. Then the temporary goes out of scope, closing whatever file we used to own (if any). Std::swap is by default implemented in terms of move, unless a more efficient specialisation is available. It is required to always work and never throw an exception. – Richard Hodges Sep 09 '16 at 09:37
  • `auto x = y;` invokes a copy constructor. It copies y into a new x. `x = y` triggers a copy-assignment, which takes a copy of y and replaces x with that copy. `auto x = std::move(y)` triggers the move constructor in which x becomes what y was and y is left in an undefined, or moved-from state. `x = std::move(y)` replaces x by moving y into it. The previous contents of x are lost, x becomes what y was and y is left in a moved-from (usually undefined) state. – Richard Hodges Sep 09 '16 at 09:49
  • @athos i've just seen the bug. fixed. My apologies. – Richard Hodges Sep 09 '16 at 09:54
  • thank you so much! Now I just don't understand the two lines about right value construction `owns_a_file_copyable(owns_a_file_copyable&&) = default;` and and assignment: `owns_a_file_copyable& operator=(owns_a_file_copyable&&) = default;` , would you mind write an example when will these two be triggered, instead of the left value copy constructor and assignment? – athos Sep 09 '16 at 10:01
  • Brilliant! Thank you! – athos Sep 09 '16 at 10:17