C++ Rule of 5 copy and move (constructor and assig

2019-08-29 18:23发布

问题:

I am writing a c++11+ standards compliant class, and it is time for me to implement the rule of 5.

  1. Destructor
  2. Copy Constructor
  3. Move Constructor
  4. Copy Assignment Operator
  5. Move Assignment Operator

I had a question about copy/move constructors/assignment. It is my understanding that a copy constructor/assignment should make a copy (shallow, deep?) of your class. In the case that your class has unique members, such as a unique_ptr, there are two scenarios I foresee.

  • Make a deep copy of the object

    I am not sure, in my case, how I would make a deep copy (see code below).

  • Move the object to the other class

    In my opinion, moving the pointer in a copy constructor has unintended side effects for a user, as they are expecting a copy, and not a move, and the original object being copied would no longer function.

    Making a copy could also be problematic, however, in my case, the curl object could contain sensitive information such as cookies or a password?


What is the defacto way to create copy and move constructors/assignment for classes with these constraints? To deep copy, to move, or not explicitly and implicitly define a copy constructor (does the delete keyword do this)?


// client.h
#pragma once

#include <Poco/URI.h>
#include <curl/curl.h>

class client
{
public:
    typedef Poco::URI uri_type;
    // Constructor
    client(const uri_type & auth);
    // Destructor
    virtual ~client();
    // Copy constructor
    client(const client & other);
    // Move constructor
    client(client && other);
    // Copy assignment
    client & operator=(const client & other);
    // Move assignment operator
    client & operator=(client && other);

private:
    uri_type auth_;
    // ... other variables (both unique and copyable) ommitted for simplicity.
    std::unique_ptr<CURL, void(*)(CURL*)> ptr_curl_;
};

// client.cpp
#include <memory>
#include <Poco/URI.h>
#include <curl/curl.h>

#include "client.h"

// Constructor
client::client(const uri_type & auth)
: auth_(auth)
, ptr_curl_(curl_easy_init(), curl_easy_cleanup)
{
    curl_global_init(CURL_GLOBAL_DEFAULT);
}

// Destructor
client::~client()
{
    curl_global_cleanup();
}

// Copy constructor
client::client(const client & other)
{
    // ... deep copy? move?
    // how would you deep copy a unique_ptr<CURL>?
}

// Move constructor
client::client(client && other)
{
    std::swap(*this, other);
}

// Copy assignment
client & client::operator=(const client & other)
{
    // Cant get this to work by making the copy happen in the parameter.
    client temp(other);
    std::swap(*this, temp);
    return *this;
}

// Move assignment operator
client & client::operator=(client && other)
{
    return *this;
}

回答1:

As the name indicates, a copy constructor/assignment operator should always COPY and not move its members, where copy usually means deep copy.

Remember: By default, all objects in c++ should have value semantics, i.e. they should behave like an int.

Furthermore, the terminology in your post indicates that you are confusing unique objects (singletons) with objects pointed to by unique_ptr. Most non-copyable objects are handlers (like unique_ptr handles objects on the heap) in which case you'd copy whatever they handle. If that is not possible, then most likely, it doesn't make sense to implement a copy constructor of your object at all.

If your object holds an owning reference to a unique resource (from which there can be only one instance in your project),then the first question would be: can it be shared? -> use shared_ptr. If not -> don't copy. If your object holds a non-owning reference to a unique ressource (raw pointer or reference) copy the reference. In both cases, be aware that you now have two objects, that share part of their state, which might be dangerous even in non-multithreaded applications.



回答2:

For some types, having a copy constructor is inappropriate. That includes most types that semantically contain non-copyable types. (Don't count the unique_ptr, do count the CURL instance)

If libcurl has a "duplicate_handle" sort of function, then your copy constructor should use it to initialize the unique_ptr in the copy. Otherwise, you should delete your copy constructor and only implement move.