Chapter 7 Copy Constructor¶
1 References¶
- 引用成员不能先声明后赋值,必须在构造函数初始化列表中完成绑定
示例
class X {
public:
int& m_y;
X(int& a);
};
X::X(int& a) : m_y(a) {}
- 函数可以返回引用,但返回的引用必须指向仍然存在的对象,通常应返回非局部对象(全局对象、静态对象、调用方传入对象、容器元素等)
示例
#include <cassert>
const int SIZE = 32;
double myarray[SIZE]; // 全局数组,生命周期和整个程序一样
double& subscript(const int i) {
return myarray[i]; // 返回全局数组第 i 个元素的引用
}
main() {
// 1. 初始化数组
for (int i = 0; i < SIZE; i++) {
myarray[i] = i * 0.5;
}
// 2. 通过函数读取数组元素的值
double value = subscript(12);
// 3. 通过函数直接修改数组元素的值
subscript(3) = 34.5;
}
禁止返回局部变量引用
double& bad_subscript(const int i) {
double temp = myarray[i];
return temp; // error
}
局部变量在函数返回后生命周期结束,此时返回的引用会变成悬空引用(dangling reference)
2 Const¶
- const 值传递通常没有意义,更常见的是 const 引用,如
Person(const string& name, int weight); - const 引用传递的优点是不修改实参、避免一次拷贝且 const 提供接口保护
示例:const 引用参数
void func(const int& y, int& z) {
z = z * 5; // ok
y += 8; // error
}
临时值是 const
void func(int&);
func(i * 3); // warning / error
可以理解为:
const int _tmp_ = i * 3;
func(_tmp_); // 非 const 引用不能绑定到临时常量
- 返回 const 值基本没意义
- 返回 const 引用和 const 指针是否需要取决于是否希望调用方修改返回对象
3 Copy Constructor¶
- 拷贝的本质:用已有对象创建新对象,常用于值传递参数、初始化新对象和函数按值返回
示例
void func(Currency p) {
cout << "X = " << p.dollars();
}
Currency bucks(100, 0);
func(bucks); // bucks 被复制到 p
- 拷贝操作是由拷贝构造函数来实现的,函数签名为
T::T(const T&); - 如果没有手动定义拷贝构造函数,C++ 编译器会自动生成默认拷贝构造函数
- 默认拷贝构造函数的默认行为是进行逐成员拷贝,对数值类型、普通对象、对象数组都是安全可用的
- 但对裸指针则只是复制地址,可能导致共享同一块内存,引发数据冲突或崩溃问题

含裸指针的类
class Person {
public:
// 构造函数:接收一个 C 风格字符串,用来初始化名字
Person(const char *s);
// 析构函数:用来释放名字占用的内存
~Person();
void print();
// ... accessor functions
private:
char *name; // 用原生 char* 而不是 std::string
};
- C 风格字符串本质是字符数组,结尾有终止符
'\0',"C++"在内存中实际占 4 个字符
<cstring> 常用函数
size_t strlen(const char* s); // 长度,不含 '\0'
char* strcpy(char* dest, const char* src); // 拷贝字符串
- 参数必须写成引用,若写成值传递,会再次触发拷贝构造,形成递归
带 char* 字符串成员的 Person 类的构造函数和析构函数实现
#include <cstring>
using namespace std;
Person::Person(const char* s) {
name = new char[::strlen(s) + 1];
::strcpy(name, s);
}
Person::~Person() {
delete[] name;
}
手动实现的拷贝构造(深拷贝)
Person::Person(const Person& w) {
name = new char[::strlen(w.name) + 1];
::strcpy(name, w.name);
}
- 拷贝构造函数可以跨对象边界访问
w.name(原对象的私有成员) - 拷贝构造函数的作用是初始化新对象的未初始化内存
What if the name was a string (and not a char*)?
#include <string>
class Person {
public:
Person( const std::string& );
~Person();
void print();
// ... 其他访问器函数(getter/setter)...
private:
std::string name; // 嵌入式对象(组合关系:Person 对象包含一个 std::string 对象)
// ...其他数据成员...
};
- 在编译器自动生成的默认拷贝构造函数中,它会递归调用所有成员对象(以及基类)的拷贝构造函数,
std::string自动管理内存,无需手动分配 / 释放,析构时自动清理
When are copy ctors called?
During call by value
void roster( Person ); // declare function
Person child( "Ruby" ); // create object
roster( child ); // call function

During initialization
Person baby_a("Fred");
// these use the copy ctor
Person baby_b = baby_a; // not an assignment
Person baby_c(baby_a); // not an assignment

During function return
Person captain() {
Person player("George");
return player;
}
Person who = captain();

- 在不改变程序语义、保证安全的前提下,编译器可以自动优化掉不必要的拷贝操作
- 程序设计时要兼容不做优化的编译器,不要依赖编译器的拷贝消除,同时要主动考虑性能优化,避免不必要的拷贝操作
示例
Person copy_func(Person p) {
p.print();
return p; // copy ctor called!
}
Person nocopy_func(char* who) {
return Person(who); // no copy needed!
}
Construction vs Assignment
- 每个对象只会被构造一次
- 每个对象应该只被销毁一次
- 一个已构造好的对象可以被赋值很多次
初始化不是赋值
Person a("Fred");
Person b = a; // 拷贝构造
Person c("Barney");
c = a; // 赋值运算
- 大多数情况下不用自己写拷贝构造
- 涉及裸指针、文件句柄等资源管理时要显式定义
- 若不希望对象可复制,可以把拷贝构造声明为
private(旧写法)或Person(const Person&) = delete;(C++11)
4 Static¶
static 的两层含义
- 静态存储期:只分配一次,程序期间一直存在
- 内部链接:名字只在当前源文件中可见
Global static hidden in file
int g_global;
static int s_local;
void func() { /* ... */ }
static void hidden() { /* ... */ }
g_global可通过extern在别处声明使用s_local/hidden()只在当前.cpp中可见
Static inside functions
void f() {
static int num_calls = 0;
num_calls++;
}
- 值在整个程序运行期都保留,但初始化只发生一次
Static applied to objects
class X {
X(int, int);
~X();
};
void f() {
static X my_X(10, 20);
}
- 当执行流第一次走到该定义处时构造(最多构造一次),程序结束时析构
Conditional construction
void f(int x) {
if (x > 10) {
static X my_X(x, x * 21);
}
}
- 只有第一次满足条件时才构造,一旦构造,后续会保留状态
Global Objects
#include "X.h"
static X global_x(12, 34);
static X global_x2(8, 16);
- 它们会在
main()之前构造,同一文件中按出现顺序构造,在main()结束或exit()调用时析构
- 静态成员变量(Static member variables):
- 属于整个类,而不是某个对象
- 只初始化一次
- 需要在
.cpp中提供定义(定义处不再写static)
- 静态成员函数(Static member functions):
- 没有隐式接收者
this,也不能声明为const - 只能访问静态成员变量和全局对象,不能参与动态绑定
- 没有隐式接收者
使用方式
<class name>::<static member>
<object variable>.<static member>