Special member functions likey to be generated by compilers are

  • default constructor
  • destructor
  • copy constructor
  • copy assignment
  • move constructor
  • move assignment

Generated functions are implicitly public and inline. They are non-virtual unless it is a destructor in a derived class inheriting from a base class with virtual destructor.

Copy operations perform memberwise copy on non-static data of the class and its base parts. Move operations perform memberwise move on non-static data of the class and its base parts.

when compiler figures out the memberwise operations are not appropriate, then it will not generate the func.

Declaring a copy ctor does not prevent the compiler from generating a copy assignment operator, and vice versa. But declaring a copy operationprevents the compiler from generating move operations. Because if memberwise copy is not appropriate, then the compoiler figures that there is no reason that memberwise move is appropriate.

And the two move operations are not independent like the two copy operations, declaring one prevents the compiler from generating the other.

Even though Rule of Three states that if you declare any of a copy constructor, copy assignment operator, or destructor, you should declare all three. This is reasonable when it comes to the classes that manage resources. But at the time of c++98, this reason was not fully appreciated. So in c++98, a user-declared destructor has no impact on compilers’ willingness to generate copy operations, i.e compilers generate copy operations. And it continues to be the case in C++11 only for the risk of breaking legacy code. But Rule of Three comes into effect in the move operaions and destructor.

So the whole picture is this: relation

When you want to use move operations, define it explicity or use = default explicity to allow compiler’s default move operations. Don’t depend on compilers, because compilers may not generate move operations for you.

The StringTable class only has one user defined ctor, so compilers will generate other special funcs.

class StringTable {
public:
    StringTable() {};
    void insert(int id, std::string value) {
        mp[id] = value;
    }
    int size() {
        return mp.size();
    }

private:
    std::map<int,std::string> mp;
};

int main () {
    StringTable st;
    for(int i = 0; i < 1000000; i++) {
        st.insert(i, "hello");
    }
    auto start = std::chrono::high_resolution_clock::now();

    StringTable st2 = std::move(st);
    auto end = std::chrono::high_resolution_clock::now();

    // calculate time cost
    std::chrono::duration<double> elapsed = end - start;

    // time in second
    std::cout << "Elapsed time: " << elapsed.count() << " seconds\n";
    std::cout << "after move, size of (st) is " << st.size() << std::endl;
    std::cout << "after move, size of (st2) is " << st2.size() << std::endl;
}

compile this progrom and run, result denotes that StringTable st2 = std::move(st); is pretty fast and move really happens.

(base) ➜  Effective_modern git:(master) ✗ g++ -std=c++11 special.cpp -o special 
(base) ➜  Effective_modern git:(master) ✗ ./special
Elapsed time: 1.83e-07 seconds
after move, size of (st) is 0
after move, size of (st2) is 1000000

But if you define a destructor or copy operations, you will incur huge performance loss.

class StringTable {
public:
    StringTable() {};
    ~StringTable() = default;
    // copy ctor (test copy)
    StringTable(const StringTable& other) : mp(other.mp) {
        std::cout << "Copy constructor called\n";
    }

    StringTable(StringTable&& other) = default; 
    void insert(int id, std::string value) {
        mp[id] = value;
    }
    int size() {
        return mp.size();
    }

private:
    std::map<int,std::string> mp;
};

int main () {
    StringTable st;
    for(int i = 0; i < 1000000; i++) {
        st.insert(i, "hello");
    }
    auto start = std::chrono::high_resolution_clock::now();

    StringTable st2 = std::move(st);
    auto end = std::chrono::high_resolution_clock::now();

    // calculate time cost
    std::chrono::duration<double> elapsed = end - start;

    // time in second
    std::cout << "Elapsed time: " << elapsed.count() << " seconds\n";
    std::cout << "after move, size of (st) is " << st.size() << std::endl;
    std::cout << "after move, size of (st2) is " << st2.size() << std::endl;
}

move doesn’t happen and accually the copy ctor takes into effect.

(base) ➜  Effective_morden git:(master) ✗ g++ -std=c++11 special.cpp -o special 
(base) ➜  Effective_morden git:(master) ✗ ./special                             
Copy constructor called
Elapsed time: 0.484521 seconds
after move, size of (st) is 1000000
after move, size of (st2) is 1000000

So try to define you own move operations and use = default if compilers-generated move operations satisfy your need to avoid the potential performance loss.

What is std::move? Does the object that calls std::move really get moved?