移动语义和拷贝语义有什么区别?什么是 C++ 中的智能指针?有哪些类型的智能指针?

1) 移动语义和拷贝语义有什么区别?   

在编程语言中,移动语义和拷贝语义有着明显的区别:

一、定义与作用

拷贝语义:

  • 是一种传统的对象复制方式。当进行拷贝操作时,会创建一个新的对象,并将原对象的内容复制到新对象中。例如,在 C++中,如果一个类具有拷贝构造函数和拷贝赋值运算符,那么就可以通过这些函数进行对象的拷贝。
  • 拷贝语义通常用于需要保留原始对象,同时创建一个与原始对象相同内容副本的情况。比如,当你需要在不同的地方使用相同的数据,但又不希望修改原始数据时,拷贝语义就很有用。

移动语义:

  • 允许将资源从一个对象转移到另一个对象,而不是进行传统的复制操作。移动语义通常在以下情况下非常有用:当对象包含大量资源(如动态分配的内存、文件句柄等)时,进行拷贝操作可能会非常昂贵,而移动语义可以高效地将资源转移,避免不必要的复制。
  • 例如在 C++中,通过移动构造函数和移动赋值运算符实现移动语义。如果一个函数返回一个大型对象,使用移动语义可以避免昂贵的拷贝操作,直接将返回值的资源转移到接收对象中。

二、性能影响

拷贝语义:

  • 如果对象较大或者包含复杂的数据结构,拷贝操作可能会消耗大量的时间和资源。因为需要逐个复制对象的成员变量,对于动态分配内存的对象,还需要进行内存分配和数据复制操作。
  • 例如,一个包含大量数据的容器类,进行拷贝操作时可能需要遍历整个容器,复制每个元素,这可能会导致性能下降。

移动语义:

  • 移动语义通常比拷贝语义更高效,因为它只是将资源的所有权从一个对象转移到另一个对象,而不需要进行深度复制。对于包含动态分配内存或其他资源的对象,移动操作可以避免重复分配内存和复制数据的开销。
  • 例如,在 C++中,使用 std::vector 的移动构造函数可以快速地将一个 vector 对象的内容转移到另一个 vector 对象中,而不需要逐个复制元素。
  • 语法和使用方式

拷贝语义:

  • 在 C++中,拷贝语义通常通过拷贝构造函数和拷贝赋值运算符实现。当一个对象被初始化或赋值给另一个对象时,如果没有定义移动构造函数和移动赋值运算符,编译器会自动调用拷贝构造函数和拷贝赋值运算符进行拷贝操作。
  • 例如:
class A
{
	int val;
	int* p;
public:
	A(const A& other)
	{
		this->val = other.val;
		this->p = new int(val);
		cout << "拷贝构造" << endl;
	}
	A& operator=(const A& other)
	{
		if (p)delete p;
		this->val = other.val;
		this->p = new int(val);
		cout << "拷贝赋值运算符" << endl;
		return *this;
	}
};

移动语义:

  • 在 C++中,移动语义通过移动构造函数和移动赋值运算符实现。当一个对象被初始化或赋值给另一个对象时,如果存在移动构造函数和移动赋值运算符,并且右值可以被移动,编译器会优先调用移动构造函数和移动赋值运算符进行移动操作。
  • 例如:
class A
{
	int val;
	int* p;
public:
	A(A &&other)
	{
		this->val = other.val;
		this->p = other.p;
		other.p = nullptr;
		cout << "移动构造" << endl;
	}
	A& operator=(A&& other)
	{
		if (p)delete p;
		this->val = other.val;
		this->p = other.p;
		other.p = nullptr;
		cout << "移动赋值运算符" << endl;
		return *this;
	}
};

移动语义和拷贝语义在定义、作用、性能影响和语法使用方式上都有明显的区别。在编写高效的程序时,合理地使用移动语义可以避免不必要的拷贝操作,提高程序的性能。

2)什么是 C++ 中的智能指针?有哪些类型的智能指针?

智能指针的作用

当使用普通指针来管理动态分配的内存时,程序员需要手动负责在合适的时候释放内存,否则可能会导致内存泄漏。而智能指针通过自动管理内存的生命周期,在适当的时候自动释放内存,大大降低了内存管理的复杂性和出错的可能性。智能指针主要以下三种类型

share_ptr(共享智能指针)

不能使用同一个地址初始化智能指针

构造函数初始化
#include<iostream>
#include<string>
#include<algorithm>
#include<memory>
using namespace std;

int main()
{
	shared_ptr<int>ptr1(new int(520));
	cout << "ptr1管理内存引用计数:" << ptr1.use_count() << endl;
	shared_ptr<char>ptr2(new char[520]);
	cout << "ptr2管理内存引用计数:" << ptr2.use_count() << endl;
	shared_ptr<int>ptr3;
	cout << "ptr3管理内存引用计数:" << ptr3.use_count() << endl;
	shared_ptr<int>ptr4(nullptr);
	cout << "ptr4管理内存引用计数:" << ptr4.use_count() << endl;

	return 0;
}

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数加1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数为0。另外,不要使用一个原始指针初始化多个shared_ptr

拷贝构造和移动构造初始化
#include<iostream>
#include<string>
#include<algorithm>
#include<memory>
using namespace std;

int main()
{
	shared_ptr<int>ptr1(new int(520));
	cout << "ptr1管理内存引用计数:" << ptr1.use_count() << endl;
	shared_ptr<int>ptr2(ptr1);
	cout << "ptr2管理内存引用计数:" << ptr2.use_count() << endl;
	shared_ptr<int>ptr3 = ptr1;
	cout << "ptr3管理内存引用计数:" << ptr3.use_count() << endl;
	shared_ptr<int>ptr4(move(ptr1));
	cout << "ptr4管理内存引用计数:" << ptr4.use_count() << endl;
	shared_ptr<int>ptr5 = move(ptr2);
	cout << "ptr5管理内存引用计数:" << ptr5.use_count() << endl;

	return 0;
}
std::make_shared初始化

通过 c++11 提供的 std::make shared()就可以完成内存对象的创建并将其初始化给智能指

#include<iostream>
#include<string>
#include<algorithm>
#include<memory>
using namespace std;

class Test 
{
public:
	Test()
	{
		cout<<"无参构造" << endl;
	}
	Test(int x)
	{
		cout << "有参构造" << endl;
	}
	Test(string str)
	{
		cout << "string类型构造" << endl;
	}
	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	shared_ptr<int>ptr1 = make_shared<int>(520);
	
	shared_ptr<Test>ptr2 = make_shared<Test>();
	
	shared_ptr<Test>ptr3 = make_shared<Test>(520);
	
	shared_ptr<Test>ptr4 = make_shared<Test>("asdjas;dlhas");
	
	shared_ptr<Test>ptr5 = move(ptr2);

	cout << "ptr1管理内存引用计数:" << ptr1.use_count() << endl;
	cout << "ptr2管理内存引用计数:" << ptr2.use_count() << endl;
	cout << "ptr3管理内存引用计数:" << ptr3.use_count() << endl;
	cout << "ptr4管理内存引用计数:" << ptr4.use_count() << endl;
	cout << "ptr5管理内存引用计数:" << ptr5.use_count() << endl;

	return 0;
}

如果使用拷贝的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用计数也会增加。如果使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用技术不会增加。

shareed_ptr的实现

模板不可以多文件编程

#pragma once
#include<iostream>
using namespace std;

template<typename T>
class Ref 
{
	T* p;
	int n;
public:
	Ref()
	{
		p = nullptr;
		n = 0;
	}
	Ref(T* p)
	{
		this->p = p;
		n = 1;
	}
	void increase()
	{
		if(p)n++;
	}
	void reduce()
	{
		if(n > 0)n--;
		if (n == 0)
		{
			if(p)delete p;
			delete this;
		}
	}
	int useCount()
	{
		return n;
	}
	T* get()
	{
		return p;
	}
};

template<typename T>
class Shared_ptr
{
	Ref<T>* r;
public:
	Shared_ptr()
	{
		r = new Ref<T>();
	}
	Shared_ptr(T* p)
	{
		r = new Ref<T>(p);
	}
	Shared_ptr(const Shared_ptr& o)
	{
		this->r = o.r;
		if(r->get())r->increase();
	}

	Shared_ptr(Shared_ptr&& o)
	{
		r = o.r;
		o.r = nullptr;
	}

	~Shared_ptr()
	{
		if(r)r->reduce();
	}

	Shared_ptr& operator =(const Shared_ptr& other)
	{
		r->reduce();
		r = other.r;
		if (r->get())r->increase();
		return *this;
	}
	Shared_ptr& operator =(Shared_ptr&& other)
	{
		r->reduce();
		r = other.r;
		other.r = nullptr;
		return *this;
	}
	int UseCount()
	{
		if (r)return r->useCount();
		else return 0;
	}
	T* get()
	{
		return r->p;
	}
	void reset()
	{
		r->reduce();
		r = nullptr;
	}
	void reset(T*p)
	{
		r->reduce();
		r = new Ref<T>(p);
	}
	T& operator *()
	{
		return *r->get();
	}
	T* operator ->()
	{
		return r->get();
	}

};

#include "Shared_ptr.h"
#include<iostream>

using namespace std;

int main()
{
	Shared_ptr<int>sp;
	cout << sp.UseCount() << endl;
	Shared_ptr<int>sp1(new int(3));
	cout << sp1.UseCount() << endl;
	Shared_ptr<int>sp2 = sp1;
	cout << sp2.UseCount() << endl;
    Shared_ptr<int> sp3(move(sp1));
    cout << sp3.UseCount() << endl;
    sp2.reset();
    cout << "重置后引用计数: " << sp1.UseCount() << endl;

	return 0;
}
weak_ptr(弱引用智能指针)

弱引用智能指针 std::weak ptr 可以看做是 shared ptr 的助手,它不管理 shared ptr 内部的指针。std::weak ptr 没有重载操作符*和->因为它不共享指针,不能操作资源所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared ptr 中管理的资源是否存在。

初始化
#include<iostream>
#include<memory>
using namespace std;

int main() {
    shared_ptr<int>sp(new int);
    //构造了一个空weak_ptr对象
    weak_ptr<int>wp1;
    //通过一个空weak_ptr对象构造了另一个空weak_ptr对象
    weak_ptr<int>wp2(wp1);
    //通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
    weak_ptr<int>wp3(sp);
    //通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象(隐式转换类型)
    weak_ptr<int>wp4;
    wp4 = sp;
    //通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象
    weak_ptr<int>wp5;
    wp5 = wp3;
    
    return 0;
}
use_count()

通过调用std::weak_ptr类提供的use_count()方法可以获得当前所观察资源的引用计数

#include<iostream>
#include<memory>
using namespace std;

int main() {
    shared_ptr<int>sp(new int);
    weak_ptr<int>wp1;
    weak_ptr<int>wp2(wp1);
    weak_ptr<int>wp3(sp);
    weak_ptr<int>wp4;
    wp4 = sp;
    weak_ptr<int>wp5;
    wp5 = wp3;
    cout << "use_count:" << endl;
    cout << "wp1:" << wp1.use_count() << endl;
    cout << "wp2:" << wp2.use_count() << endl;
    cout << "wp3:" << wp3.use_count() << endl;
    cout << "wp4:" << wp4.use_count() << endl;
    cout << "wp5:" << wp5.use_count() << endl;
    return 0;
}

通过打印结果可知,虽然弱引用智能指针wp3,wp4,wp5监测的资源是同一个,但是其引用技术没有变化,进一步证明了weak_ptr只是监测资源,并不管理资源

expired()

通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放

#include<iostream>
#include<memory>
using namespace std;

int main() {
    shared_ptr<int>shared(new int(10));
    weak_ptr<int>weak(shared);
    cout << "1.weak" << (weak.expired() ? "is" : "is not") << "expired" << endl;
    shared.reset();
    cout << "2.weak" << (weak.expired() ? "is" : "is not") << "expired" << endl;

    return 0;
}

weak_ptr监测就是shared_ptr管理得内存,当共享智能指针调用shared.reset();之后管理资源被释放,因此weak.expired()函数的结果返回为true,表示监测得资源已经不存在了

lock()

通过调用std::weak_ptr类提供的lock()方法来获取管理所检测资源的shared_ptr对象

#include<iostream>
#include<memory>
using namespace std;

int main() {
    shared_ptr<int>sp1,sp2;
    weak_ptr<int>wp;
    sp1 = std::make_shared<int>(520);
    wp = sp1;
    sp2 = wp.lock();
    cout << "use_count:" << wp.use_count() << endl;
    sp1.reset();
    cout << "use_count" << wp.use_count() << endl;
    sp1 = wp.lock();
    cout << "use_count:" << wp.use_count() << endl;

    cout << "sp1:" << *sp1 << endl;
    cout << "sp2:" << *sp2 << endl;
    return 0;
}
  1. sp2 = wp.lock();通过调用lock()方法得到一个用于管理weak_ptr对象所检测的资源共享智能指针对象,使用这个对象初始化sp2,此时所监测资源的引用计数为2
  2. sp1.reset();共享智能指针sp1被重置,weak_ptr对象所监测的资源引用计数减1
  3. sp1 = wp.lock();sp1重新被初始化,并且管理还是weak_ptr对象所监测的资源,因此引用计数加1

共享智能指针对象sp1和sp2管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是520

*返回管理this的shared_ptr
#include <iostream>
#include <memory>
using namespace std;
struct Test
{
    shared ptr<Test>getSharedPtr()
    {
        return shared ptr<Test>(this)
    }
    ~Test()
    {
        cout<<"析构函数"<< endl;
    }
};
int main()
shared ptr<Test>spl(new Test);
cout<<"引用个数"<< spl.use count()<< endl;
shared ptr<Test>sp2=sp1->getSharedPtr();
cout<<"引用个数:"<<spl.use count()<< endl;
return 0:

通过输出的结果可以看到一个对象被析构两次,原因是:这个例子中使用同一个this构造了两个智能指针对象sp1和sp2,这二者之间是没有任何关系的,因为sp2并不是通过sp1初始化得到的实例对象。在离开作用域之后this将被构造的两个智能指针各自析构,导致重复释放内存

*循环引用问题
#include <iostream>
#include <memory>
using namespace std;

class A;
class B;

class A
{
public:
	//shared_ptr<B> bptr; //错误
    weak_ptr<B> bptr;
	~A()
	{
		cout << "class TA is disstruct ..." << endl;
	}
};

class B
{
public:
	//shared_ptr<A> aptr;
    weak_ptr<A> aptr;
	~B()
	{
		cout << "class TB is disstruct ..." << endl;
	}
};

void testPtr()
{
	shared_ptr<A> ap(new A);
	shared_ptr<B> bp(new B);
	cout << "A 的 引用计数: " << ap.use_count() << endl;
	cout << "B 的 引用计数: " << bp.use_count() << endl;

	ap->bptr = bp;
	bp->aptr = ap;
	cout << "A 的 引用计数: " << ap.use_count() << endl;
	cout << "B 的 引用计数: " << bp.use_count() << endl;
}

int main()
{
	testPtr();
	return 0;
}

共享智能指针ap,bp对A\B实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致A,B的实例对象不能被析构,最终造成内存泄漏。通过使用wea_ptr可以解决这个问题,只需将类A或类B的任意一个成员改为weak_ptr。

unique_ptr(独占智能指针)
初始化

它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针但不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr

#include<iostream>
#include<string>
#include<algorithm>
#include<memory>
using namespace std;

int main()
{
	unique_ptr<int>ptr(new int(10));
	//报错
	unique_ptr<int>ptr2 = ptr1;
	return ;
}

unique_ptr不允许被复制,但是可以通过函数返回给其他的unique_ptr,还可以通过move()转移给其他的unique_ptr。还是一个unique_ptr独占一个地址。

使用reset方法可以让unique_ptr解除对原始内存的管理,也可以用来初始化一个独占智能指针。

#include<iostream>
#include<string>
#include<algorithm>
#include<memory>
using namespace std;

int main()
{
	unique_ptr<int>ptr1(new int(10));
	unique_ptr<int>ptr2;
	ptr1.reset();//解除对原始内存的管理
	ptr2.reset(new int(250));//重新指定智能指针管理的原始内存

	return ;
}

如果想要获取独占智能指针管理的原始地址,可以调用get()方法

#include<iostream>
#include<string>
#include<algorithm>
#include<memory>
using namespace std;

int main()
{
	unique_ptr<int>ptr1(new int(10));
	unique_ptr<int>ptr2 = move(ptr1);
	ptr2.reset(new int(250));
	cout << *ptr2.get() << endl;//得到内存地址中存储的实际数值250
	return ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值