作为值类型的 C++ 类

默认情况下,C++ 类是值类型。 可以将其指定为引用类型,使多态行为支持面向对象的编程。 值类型有时从内存和布局控件的角度进行查看,而引用类型与基类和虚拟函数有关,用于多态目的。 默认情况下,值类型可以复制,这意味着总是有一个复制构造函数和一个复制赋值运算符。 对于引用类型,将类设为不可复制(禁用复制构造函数和复制赋值运算符),并使用支持其预期多态性的虚拟析构函数。 值类型还与内容有关,复制时,总是提供可单独修改的两个独立值。 引用类型与标识有关 - 它是哪种类型的对象? 因此,“引用类型”也称为“多态类型”。

如果确实需要类似引用的类型(基类、虚拟函数),则需要显式禁用复制,如以下代码中的 MyRefType 类所示。

// cl /EHsc /nologo /W4

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

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

编译上述代码将导致以下错误:

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'

值类型和移动效率

由于新的复制优化,避免了复制分配开销。 例如,在字符串向量中间插入字符串时,没有复制重新分配开销,只有一次移动,即使它会导致向量本身的增长。 这些优化也适用于其他操作:例如,对两个巨大的对象执行添加操作。 如何启用这些值操作优化? 编译器可以隐式启用它们,就像编译器自动生成复制构造函数一样。 但是,类必须通过在类定义中声明移动赋值和移动构造函数“选择加入”它们。 移动在适当的成员函数声明和定义移动构造函数和移动赋值方法中使用双与号 (&&) rvalue 引用。 还需要插入正确的代码,以从源对象中“窃取内容”。

如何确定是否需要启用移动操作? 如果你已经知道需要启用复制构造,你可能也希望启用移动构造,特别是它比深层副本便宜的情况。 但是,如果知道需要移动支持,这不一定意味着要启用复制操作。 后一种情况称为“仅移动类型”。 标准库中已有的示例是 unique_ptr。 顺便说一下,旧的 auto_ptr 已被弃用,并被 unique_ptr 取代,这正是由于以前版本的 C++ 中缺乏移动语义支持。

通过使用移动语义,可以按值返回或在中间插入。 移动是复制的优化。 无需将堆分配作为解决方法。 请看下面的伪代码:

#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

为适当的值类型启用移动

对于类似于值的类,移动比深度副本便宜,启用移动构造和移动赋值以提高效率。 请看下面的伪代码:

#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!");
    }
};

如果启用复制构造/赋值,也会启用移动构造/赋值(如果它比深度副本便宜)。

某些非值类型是仅移动类型,例如,无法克隆资源时,仅传输所有权。 示例:unique_ptr

另请参阅

C++ 类型系统
欢迎回到 C++
C++ 语言参考
C++ 标准库