13.1. The Copy Constructor13.1. 复制构造函数The constructor that takes a single parameter that is a (usually const) reference to an object of the class type itself is called the copy constructor. Like the default constructor, the copy constructor can be implicitly invoked by the compiler. The copy constructor is used to 只有单个形参,而且该形参是对本类类型对象的引用(常用 const 修饰),这样的构造函数称为复制构造函数。与默认构造函数一样,复制构造函数可由编译器隐式调用。复制构造函数可用于:
Forms of Object Definition对象的定义形式Recall that C++ supports two forms of initialization (Section 2.3.3, p. 48): direct and copy. Copy-initialization uses the = symbol, and direct-initialization places the initializer in parentheses. 回忆一下,C++ 支持两种初始化形式(第 2.3.3 节):直接初始化和复制初始化。复制初始化使用 = 符号,而直接初始化将初始化式放在圆括号中。 The copy and direct forms of initialization, when applied to objects of class type, are subtly different. Direct-initialization directly invokes the constructor matched by the arguments. Copy-initialization always involves the copy constructor. Copy-initialization first uses the indicated constructor to create a temporary object (Section 7.3.2, p. 247). It then uses the copy constructor to copy that temporary into the one we are creating: 当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象(第 7.3.2 节),然后用复制构造函数将那个临时对象复制到正在创建的对象: string null_book = "9-999-99999-9"; // copy-initialization string dots(10, '.'); // direct-initialization string empty_copy = string(); // copy-initialization string empty_direct; // direct-initialization For objects of class type, copy-initialization can be used only when specifying a single argument or when we explicitly build a temporary object to copy. 对于类类型对象,只有指定单个实参或显式创建一个临时对象用于复制时,才使用复制初始化。 When dots is created, the string constructor that takes a count and a character is called and directly initializes the members in dots. To create null_book, the compiler first creates a temporary by invoking the string constructor that takes a C-style character string parameter. The compiler then uses the string copy constructor to initialize null_book as a copy of that temporary. 创建 dots 时,调用参数为一个数量和一个字符的 string 构造函数并直接初始化 dots 的成员。创建 null_book 时,编译器首先调用接受一个 C 风格字符串形参的 string 构造函数,创建一个临时对象,然后,编译器使用 string 复制构造函数将 null_book 初始化为那个临时对象的副本。 The initialization of empty_copy and empty_direct both call the string default constructor. In the first case, the default constructor creates a temporary object, which is then used by the copy constructor to initialize empty_copy. In the second case, the default constructor is run directly on empty_direct. empty_copy 和 empty_direct 的初始化都调用默认构造函数。对前者初始化时,默认构造函数函数创建一个临时对象,然后复制构造函数用该对象初始化 empty_copy。对后者初始化时,直接运行 empty_direct 的默认构造函数。 The copy form of initialization is primarily supported for compatibility with C usage. When it can do so, the compiler is permitted (but not obligated) to skip the copy constructor and create the object directly. 支持初始化的复制形式主要是为了与 C 的用法兼容。当情况许可时,可以允许编译器跳过复制构造函数直接创建对象,但编译器没有义务这样做。 Usually the difference between direct- or copy-initialization is at most a matter of low-level optimization. However, for types that do not support copying, or when using a constructor that is nonexplicit (Section 12.4.4, p. 462) the distinction can be essential: 通常直接初始化和复制初始化仅在低级别上存在差异。然而,对于不支持复制的类型,或者使用非 explicit 构造函数(第 12.4.4 节)的进修,它们有本质区别: ifstream file1("filename"); // ok: direct initialization ifstream file2 = "filename"; // error: copy constructor is private // This initialization is okay only if // the Sales_item(const string&) constructor is not explicit Sales_item item = string("9-999-99999-9"); The initialization of file1 is fine. The ifstream class defines a constructor that can be called with a C-style string. That constructor is used to initialize file1. file1 的初始化是正确的。ifstream 类定义了一个可用 C 风格字符串调用的构造函数,使用该构造函数初始化 file1。 The seemingly equivalent initialization of file2 uses copy-initialization. That definition is not okay. We cannot copy objects of the IO types (Section 8.1, p. 287), so we cannot use copy-initialization on objects of these types. 看上去等效的 file2 初始化使用复制初始化,但该定义不正确。由于不能复制 IO 类型的对象(第 8.1 节),所以不能对那些类型的对象使用复制初始化。 Whether the initialization of item is okay depends on which version of our Sales_item class we are using. Some versions define the constructor that takes a string as explicit. If the constructor is explicit, then the initialization fails. If the constructor is not explicit, then the initialization is fine. item 的初始化是否正确,取决于正在使用哪个版本的 Sales_item 类。某些版本将参数为一个 string 的构造函数定义为 explicit。如果构造函数是显式的,则初始化失败;如果构造函数不是显式的,则初始化成功。 Parameters and Return Values形参与返回值As we know, when a parameter is a nonreference type (Section 7.2.1, p. 230), the argument is copied. Similarly, a nonreference return value (Section 7.3.2, p. 247) is returned by copying the value in the return statement. 正如我们所知,当形参为非引用类型(第 7.2.1 节)的时候,将复制实参的值。类似地,以非引用类型作返回值时,将返回 return 语句 中的值的副本(第 7.3.2 节)。 When the parameter or return type is a class type, the copy is done by the copy constructor. For example, consider our make_plural function from page 248: 当形参或返回值为类类型时,由复制构造函数进行复制。例如,考虑 第 7.3.2 节的 make_plural 函数: // copy constructor used to copy the return value; // parameters are references, so they aren't copied string make_plural(size_t, const string&, const string&); This function implicitly uses the string copy constructor to return the plural version of a given word. The parameters are const references; they are not copied. 这个函数隐式使用 string 复制构造函数返回给定单词的复数形式。形参是 const 引用,不能复制。 Initializing Container Elements初始化容器元素The copy constructor is used to initialize the elements in a sequential container. For example, we can initialize a container using a single parameter that represents a size (Section 3.3.1, p. 92). This form of construction uses both the default constructor and the copy constructor for the element container: 复制构造函数可用于初始化顺序容器中的元素。例如,可以用表示容量的单个形参来初始化容器(第 3.3.1 节)。容器的这种构造方式使用默认构造函数和复制构造函数:
// default string constructor and five string copy constructors invoked
vector<string> svec(5);
The compiler initializes svec by first using the default string constructor to create a temporary value. The copy constructor is then used to copy the temporary into each element of svec. 编译器首先使用 string 默认构造函数创建一个临时值来初始化 svec,然后使用复制构造函数将临时值复制到 svec 的每个元素。
Constructors and Array Elements构造函数与数组元素If we provide no element initializers for an array of class type, then the default constructor is used to initialize each element. However, if we provide explicit element initializers using the normal brace-enclosed array initialization list (Section 4.1.1, p. 111), then each element is initialized using copy-initialization. An element of the appropriate type is created from the specified value, and then the copy constructor is used to copy that value to the corresponding element: 如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。然而,如果使用常规的花括号括住的数组初始化列表(第 4.1.1 节)来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素: Sales_item primer_eds[] = { string("0-201-16487-6"), string("0-201-54848-8"), string("0-201-82470-1"), Sales_item() }; A value that can be used to invoke a single-argument constructor for the element type can be specified directly, as in the initializers for the first three elements. If we wish to specify no arguments or multiple arguments, we need to use the full constructor syntax, as we do in the initializer for the last element. 如前三个元素的初始化式中所示可以直接指定一个值,用于调用元素类型的单实参构造函数。如果希望不指定实参或指定多个实参,就需要使用完整的构造函数语法,正如最后一个元素的初始化那样。 13.1.1. The Synthesized Copy Constructor13.1.1. 合成的复制构造函数If we do not otherwise define the copy constructor, the compiler synthesizes one for us. Unlike the synthesized default constructor (Section 12.4.3, p. 458), a copy constructor is synthesized even if we define other constructors. The behavior of the synthesized copy constructor is to memberwise initialize the new object as a copy of the original object. 如果我们没有定义复制构造函数,编译器就会为我们合成一个。与合成的默认构造函数(第 12.4.3 节)不同,即使我们定义了其他构造函数,也会合成复制构造函数。合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。 By memberwise, we mean that taking each nonstatic member in turn, the compiler copies the member from the existing object into the one being created. With one exception, the type of each member determines what it means to copy it. The synthesized copy constructor directly copies the value of members of built-in type. Members of class type are copied by using the copy constructor for that class. The one exception concerns array members. Even though we ordinarily cannot copy an array, if a class has a member that is an array, then the synthesized copy constructor will copy the array. It does so by copying each element. 所谓“逐个成员”,指的是编译器将现在对象的每个非 static 成员,依次复制到正创建的对象。只有一个例外,每个成员搂类型决定了复制该成员的含义。合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。数组成员的复制是个例外。虽然一般不能复制数组,但如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。 The simplest conceptual model of memberwise initialization is to think of the synthesized copy constructor as one in which each data member is initialized in the constructor initializer list. For example, given our Sales_item class, which has three data members 逐个成员初始化最简单的概念模型是,将合成复制构造函数看作这样一个构造函数:其中每个数据成员在构造函数初始化列表中进行初始化。例如,对于我们的 Sales_item 类,它有三个数据成员:
class Sales_item {
// other members and constructors as before
private:
std::string isbn;
int units_sold;
double revenue;
};
the synthesized Sales_item copy constructor would look something like: 合成复制构造函数如下所示: Sales_item::Sales_item(const Sales_item &orig): isbn(orig.isbn), // uses string copy constructor units_sold(orig.units_sold), // copies orig.units_sold revenue(orig.revenue) // copy orig.revenue { } // empty body 13.1.2. Defining Our Own Copy Constructor13.1.2. 定义自己的复制构造函数The copy constructor is the constructor that takes a single parameter that is a (usually const) reference to the class type: 复制构造函数就是接受单个类类型引用形参(通常用 const 修饰)的构造函数: class Foo { public: Foo(); // default constructor Foo(const Foo&); // copy constructor // ... }; Usually the parameter is a const reference, although we can also define the copy constructor to take a nonconst reference. Because the constructor is used (implicitly) to pass and return objects to and from functions, it usually should not be made explicit (Section 12.4.4, p. 462). The copy constructor should copy the members from its argument into the object that is being constructed. 虽然也可以定义接受非 const 引用的复制构造函数,但形参通常是一个 const 引用。因为用于向函数传递对象和从函数返回对象,该构造函数一般不应设置为 explicit(第 12.4.4 节)。复制构造函数应将实参的成员复制到正在构造的对象。 For many classes, the synthesized copy constructor does exactly the work that is needed. Classes that contain only members that are of class type or members that are of built-in (but not pointer type) often can be copied without explicitly defining the copy constructor. 对许多类而言,合成复制构造函数只完成必要的工作。只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义复制构造函数,也可以复制。 However, some classes must take control of what happens when objects are copied. Such classes often have a data member that is a pointer or that represents another resource that is allocated in the constructor. Other classes have bookkeeping that must be done whenever a new object is created. In both these cases, the copy constructor must be defined. 然而,有些类必须对复制对象时发生的事情加以控制。这样的类经常有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义复制构造函数。 Often the hardest part about defining a copy constructor is recognizing that a copy constructor is needed. Defining the constructor is usually pretty easy once the need for the constructor is recognized. The copy constructor itself is defined like any other constructor: It has the same name as the name of the class, it has no return value, it may (should) use a constructor initializer to initialize the members of the newly created object, and it may do any other necessary work inside a function body. 通常,定义复制构造函数最困难的部分在于认识到需要复制构造函数。只要能认识到需要复制构造函数,定义构造函数一般非常简单。复制构造函数的定义与其他构造函数一样:它与类同名,没有返回值,可以(而且应该)使用构造函数初始化列表初始化新创建对象的成员,可以在函数体中做任何其他必要工作。 We'll look at examples of classes that require class-defined copy constructors in later sections. Section 13.4 (p. 486) looks at a pair of classes that require an explicit copy constructor to handle bookkeeping associated with a simple message-handling application; classes with members that are pointers are covered in Section 13.5 (p. 492). 后续章节中将给出一些需要定义复制构造函数的类的例子。第 13.4 节给出了一对类,它们需要显式复制构造函数,用于处理与简单消息处理应用程序相关的工作。具有指针成员的类在第 13.5 节给出。
13.1.3. Preventing Copies13.1.3. 禁止复制Some classes need to prevent copies from being made at all. For example, the iostream classes do not permit copying (Section 8.1, p. 287). It might seem that if we want to forbid copies, we could omit the copy constructor. However, if we don't define a copy constructor, the compiler will synthesize one. 有些类需要完全禁止复制。例如,iostream 类就不允许复制(第 8.1 节)。如果想要禁止复制,似乎可以省略复制构造函数,然而,如果不定义复制构造函数,编译器将合成一个。
If the copy constructor is private, then user code will not be allowed to copy objects of the class type. The compiler will reject any attempt to make a copy. 如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。 However, the friends and members of the class could still make copies. If we want to prevent copies even within the friends and members, we can do so by declaring a (private) copy constructor but not defining it. 然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其定义。 It is legal to declare but not define a member function. However, any attempt to use an undefined member results in a link-time failure. By declaring (but not defining) a private copy constructor, we can forestall any attempt to copy an object of the class type: Attempts to make copies in user code will be flagged as an error at compile time, and attempts to make copies in member functions or friends will result in an error at link time. 声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试将导致链接失败。通过声明(但不定义)private 复制构造函数,可以禁止任何复制类类型对象的尝试:用户代码中复制尝试将在编译时标记为错误,而成员函数和友元中的复制尝试将在链接时导致错误。 Most Classes Should Define Copy and Default Constructors大多数类应定义复制构造函数和默认构造函数Classes that do not define the default constructor and/or the copy constructor impose serious limits on users of the class. Objects of classes that do not allow copies may be passed to (or returned from) a function only as a reference. They also may not be used as elements in a container. 不定义复制构造函数和/或默认构造函数,会严重局限类的使用。不允许复制的类对象只能作为引用传递给函数或从函数返回,它们也不能用作容器的元素。
|