C++ classes as value types

C++ classes are, by default, value types. They can be specified as reference types, which enable polymorphic behavior to support object-oriented programming. Value types are sometimes viewed from the perspective of memory and layout control, whereas reference types are about base classes and virtual functions for polymorphic purposes. By default, value types are copyable, which means there's always a copy constructor and a copy assignment operator. For reference types, you make the class non-copyable (disable the copy constructor and copy assignment operator) and use a virtual destructor, which supports their intended polymorphism. Value types are also about the contents, which, when they're copied, always give you two independent values that can be modified separately. Reference types are about identity - what kind of object is it? For this reason, "reference types" are also referred to as "polymorphic types".

If you really want a reference-like type (base class, virtual functions), you need to explicitly disable copying, as shown in the MyRefType class in the following code.

// cl /EHsc /nologo /W4

class MyRefType {
private:
    MyRefType & operator=(const MyRefType &);
    MyRefType(const MyRefType &);
public:
    MyRefType () {}
};

int main()
{
    MyRefType Data1, Data2;
    // ...
    Data1 = Data2;
}

Compiling the above code will result in the following error:

test.cpp(15) : error C2248: 'MyRefType::operator =' : cannot access private member declared in class 'MyRefType'
        meow.cpp(5) : see declaration of 'MyRefType::operator ='
        meow.cpp(3) : see declaration of 'MyRefType'

Value types and move efficiency

Copy allocation overhead is avoided due to new copy optimizations. For example, when you insert a string in the middle of a vector of strings, there's no copy reallocation overhead, only a move, even if it results in growth of the vector itself. These optimizations also apply to other operations: for instance, performing an add operation on two huge objects. How do you enable these value operation optimizations? The compiler enables them for you implicitly, much like copy constructors can be automatically generated by the compiler. However, your class has to "opt-in" to move assignments and move constructors by declaring them in your class definition. Move uses the double ampersand (&&) rvalue reference in the appropriate member function declarations and defining move constructor and move assignment methods. You also need to insert the correct code to "steal the guts" out of the source object.

How do you decide if you need to enable move operations? If you already know you need to enable copy construction, you probably want to enable move construction, too, especially if it's cheaper than a deep copy. However, if you know you need move support, it doesn't necessarily mean you want to enable copy operations. This latter case would be called a "move-only type". An example already in the standard library is unique_ptr. As a side note, the old auto_ptr is deprecated, and was replaced by unique_ptr precisely due to the lack of move semantics support in the previous version of C++.

By using move semantics, you can return-by-value or insert-in-middle. Move is an optimization of copy. There's no need for heap allocation as a workaround. Consider the following pseudocode:

#include <set>
#include <vector>
#include <string>
using namespace std;

//...
set<widget> LoadHugeData() {
    set<widget> ret;
    // ... load data from disk and populate ret
    return ret;
}
//...
widgets = LoadHugeData();   // efficient, no deep copy

vector<string> v = IfIHadAMillionStrings();
v.insert( begin(v)+v.size()/2, "scott" );   // efficient, no deep copy-shuffle
v.insert( begin(v)+v.size()/2, "Andrei" );  // (just 1M ptr/len assignments)
//...
HugeMatrix operator+(const HugeMatrix& , const HugeMatrix& );
HugeMatrix operator+(const HugeMatrix& ,       HugeMatrix&&);
HugeMatrix operator+(      HugeMatrix&&, const HugeMatrix& );
HugeMatrix operator+(      HugeMatrix&&,       HugeMatrix&&);
//...
hm5 = hm1+hm2+hm3+hm4+hm5;   // efficient, no extra copies

Enabling move for appropriate value types

For a value-like class where move can be cheaper than a deep copy, enable move construction and move assignment for efficiency. Consider the following pseudocode:

#include <memory>
#include <stdexcept>
using namespace std;
// ...
class my_class {
    unique_ptr<BigHugeData> data;
public:
    my_class( my_class&& other )   // move construction
        : data( move( other.data ) ) { }
    my_class& operator=( my_class&& other )   // move assignment
    { data = move( other.data ); return *this; }
    // ...
    void method() {   // check (if appropriate)
        if( !data )
            throw std::runtime_error("RUNTIME ERROR: Insufficient resources!");
    }
};

If you enable copy construction/assignment, also enable move construction/assignment if it can be cheaper than a deep copy.

Some non-value types are move-only, such as when you can't clone a resource, only transfer ownership. Example: unique_ptr.

See also

C++ type system
Welcome back to C++
C++ Language Reference
C++ Standard Library