C++中值语义、引用语义、深拷贝和浅拷贝

MirrorYuChen
MirrorYuChen
发布于 2025-01-03 / 12 阅读
0
0

C++中值语义、引用语义、深拷贝和浅拷贝

C++中值语义、引用语义、深拷贝和浅拷贝

1.值语义和引用语义

1.1 值语义

​ 值语义指的是经过拷贝操作(x=y)后,得到的新对象 x与原对象 y之间相互独立,y状态改变不会影响到 xC++内置类型及标准库提供的(模板)类大部分都是值语义的,例如:

  • 内置类型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依赖于原对象 yy状态改变会直接影响到 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

3.参考资料


评论