C++中值语义、引用语义、深拷贝和浅拷贝
1.值语义和引用语义
1.1 值语义
值语义指的是经过拷贝操作(x=y
)后,得到的新对象 x
与原对象 y
之间相互独立,y
状态改变不会影响到 x
。C++
内置类型及标准库提供的(模板)类大部分都是值语义的,例如:
- 内置类型:
bool,int, double, float, char,...
- 标准库:
std::vector, std::basic_string, std::pair, std::map,...
#include <iostream>
#include <string>
#include <vector>
int main(int argc, char *argv[]) {
{
int a = 10;
int b = a;
b = 20;
std::cout << "a = " << a << " address: " << &a << std::endl;
std::cout << "b = " << b << " address: " << &b << std::endl;
}
{
std::string s1 = "hello";
std::string s2 = s1;
s2 = "world";
std::cout << "s1 = " << s1 << " address: " << &s1 << std::endl;
std::cout << "s2 = " << s2 << " address: " << &s2 << std::endl;
}
{
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1;
v2.push_back(4);
std::cout << "v1 = " << v1.size() << " address: " << &v1[0] << std::endl;
std::cout << "v2 = " << v2.size() << " address: " << &v2[0] << std::endl;
}
return 0;
}
输出结果:
a = 10 address: 000000D71A0FFA84
b = 20 address: 000000D71A0FFAA4
s1 = hello address: 000000D71A0FFAC8
s2 = world address: 000000D71A0FFB08
v1 = 3 address: 0000029BA45E4BF0
v2 = 4 address: 0000029BA45E5050
1.2 引用语义
相对应的引用语义指的是经过拷贝操作(x=y
)后,得到的新对象 x
依赖于原对象 y
,y
状态改变会直接影响到 x
。
#include <iostream>
#include <memory>
struct A {
int x;
int y;
std::shared_ptr<int> ptr;
};
int main(int argc, char *argv[]) {
{
A a {
1, 2, std::make_shared<int>(3)
};
A b = a;
b.x = 3;
b.y = 4;
*(b.ptr) = 5;
std::cout << "a: " << a.x << " " << a.y << " " << *(a.ptr) << std::endl;
std::cout << "b: " << b.x << " " << b.y << " " << *(b.ptr) << std::endl;
}
return 0;
}
输出结果如下:
a: 1 2 5
b: 3 4 5
这里 ptr
是引用语义,A a = b;
浅拷贝后,a.ptr和b.ptr
引用相同一个int变量。需要注意一下,值语义与引用语义只是两种不同需求,存在指针成员变量并不一定代表引用语义,如,std::vector
中缓存区便是以指针存储的,但是 vector
是值语义的。
2.深拷贝与浅拷贝
2.1 深拷贝
理解了上面的值语义与引用语义后,深拷贝与浅拷贝就很好理解了。深拷贝其实就是对象具有值语义,使用赋值操作或拷贝构造时,对象实例本身会有自己独立的内存空间,任何对新对象实例属性地修改都不会影响到原对象实例属性。
那么怎么理解存在指针成员变量并不一定代码引用语义呢?举例说明一下:
#include <iostream>
#include <memory>
class Vec {
public:
Vec() : size_(0), data_(nullptr) {}
explicit Vec(size_t size) : size_(size) {
std::cout << "Creating..." << std::endl;
data_ = new int[size];
}
~Vec() {
delete[] data_;
size_ = 0;
}
Vec(const Vec &other) : size_(other.size_) {
std::cout << "Copying..." << std::endl;
data_ = new int[size_];
memcpy(data_, other.data_, size_ * sizeof(int));
}
Vec &operator=(const Vec &other) {
std::cout << "Copying operator=..." << std::endl;
if (this == &other) {
return *this;
}
delete[] data_;
size_ = other.size_;
data_ = new int[size_];
memcpy(data_, other.data_, size_ * sizeof(int));
return *this;
}
int &operator[](size_t index) {
return data_[index];
}
const int &operator[](size_t index) const {
return data_[index];
}
private:
int *data_;
size_t size_;
};
int main(int argc, char *argv[]) {
Vec a(10);
for (int i = 0; i < 10; ++i) {
a[i] = i;
}
Vec b = a;
for (int i = 0; i < 10; ++i) {
b[i] = 2 * i;
}
Vec c;
c = b;
for (int i = 0; i < 10; ++i) {
c[i] = 3 * i;
}
for (int i = 0; i < 10; ++i) {
std::cout << a[i] << " " << b[i] << " " << c[i] << std::endl;
}
return 0;
}
运行结果:
0 0 0
1 2 3
2 4 6
3 6 9
4 8 12
5 10 15
6 12 18
7 14 21
8 16 24
9 18 27
2.2 浅拷贝
浅拷贝其实就是对象具有引用语义,使用赋值操作或拷贝构造时,新构造对象会与原对象共用部分或全部资源,任何对新对象实例属性地修改都会直接影响原对象实例属性。同样的,也可以对上面的 Vec
进行改造,将拷贝构造和赋值操作变成浅拷贝:
#include <iostream>
#include <memory>
class Vec {
public:
Vec() : size_(0), data_(nullptr) {}
explicit Vec(size_t size) : size_(size) {
std::cout << "Creating..." << std::endl;
data_ = std::shared_ptr<int>(new int[size], std::default_delete<int[]>());
std::cout << "data_.use_count(): " << data_.use_count() << std::endl;
}
~Vec() {
size_ = 0;
}
Vec(const Vec &other) : size_(other.size_) {
std::cout << "Copying..." << std::endl;
data_ = other.data_;
std::cout << "data_.use_count(): " << data_.use_count() << std::endl;
}
Vec &operator=(const Vec &other) {
std::cout << "Copying operator=..." << std::endl;
if (this == &other) {
return *this;
}
size_ = other.size_;
data_ = other.data_;
std::cout << "data_.use_count(): " << data_.use_count() << std::endl;
return *this;
}
int &operator[](size_t index) {
return data_.get()[index];
}
const int &operator[](size_t index) const {
return data_.get()[index];
}
private:
std::shared_ptr<int>data_;
size_t size_;
};
int main(int argc, char *argv[]) {
Vec a(10);
for (int i = 0; i < 10; ++i) {
a[i] = i;
}
Vec b = a;
for (int i = 0; i < 10; ++i) {
b[i] = 2 * i;
}
Vec c;
c = b;
for (int i = 0; i < 10; ++i) {
c[i] = 3 * i;
}
for (int i = 0; i < 10; ++i) {
std::cout << a[i] << " " << b[i] << " " << c[i] << std::endl;
}
return 0;
}
输出结果:
Creating...
data_.use_count(): 1
Copying...
data_.use_count(): 2
Copying operator=...
data_.use_count(): 3
0 0 0
3 3 3
6 6 6
9 9 9
12 12 12
15 15 15
18 18 18
21 21 21
24 24 24
27 27 27