c++11 shared_ptr & unique_ptr & move semantics(右值引用) & emplace & lock_guard & final 和 override 关键字

std::unique_ptr & std::shared_ptr:

使用任何指针是都要考虑ownership+memory-management+lifetime这几个问题。
who is the owner of this object? is there one owner or many? who will responese for the deletion?

Just read it

smart_ptr: https://mbevin.wordpress.com/2012/11/18/smart-pointers/

使用unique_ptr则表示该对象为owner,不能copy但可以转移(std::move),因此通过std::move也可以将unique_ptr放入容器中—— vec.push_back(std::move(uptr)); 或者 v.emplace_back(std::make_unique<Foo>(/* ... */));

声明一个 std::unique_ptr 时可以只是前置声明一个类 而不是提供完整的定义,但是在构造一个 std::unique_ptr(or boost::scoped_ptr) 对象的时候,必须见到类的完整声明,否则编译器报 incomplete type 错误。要么定义包含该unique_prt的类的析构函数(but, 如果定义了析构函数就会导致编译器不会默认生成 copy operation 和 move operation等函数,这些都需要自己显示定义…),或者提供完整的unique_ptr模板参数类声明——毕竟unique_ptr需要知道怎么删除他所指向的对象。

shared_ptr则不需要这么做,因为 shared_ptr有一个type-erased 的 deleter,而当真正的shared_ptr构造完成后,他也就知道怎么删除这个对象了,而这个 deleter 也就消失了。那为什么unique_ptr不这么做呢呢?因为性能原因嘛, unique_ptr的性能跟raw pointer差不多——google “Incomplete types and shared_ptr unique_ptr ”

可以通过智能指针来获得普通指针,但是 plain pointers should never be used to act as the ‘owners’ of objects.

结论就是:当你确定这个就是owner的时候,使用unique_ptr;当你不确定这是不是owner的时候,使用 shared_ptr(如果韩式的参数是shared_ptr 则注意传值与传 const 引用的区别);只有当你确定这个不是owner的时候你才使用 raw poiter

关于函数为什么能够通过返回值(而不是引用)的形式返回一个 std::unique_ptr 的对象;不会导致被复制(也不能被复制),并且不会生成临时导致该对象被析构,可以参见这篇博文,返回值不需要被move 部分(即NVO——返回值优化):https://blog.csdn.net/GW569453350game/article/details/47080357

还需要注意的一点是,一个 shared_ptr/unique_ptr 是否为 null 和是否 valid 是有区别的:
例如,一个默认构造的 shared_ptr 等价于 reset 为空的值,其表现为 nullptr; 其 .get() 方法返回的也是 nullptr
但是,一个本来有效,但是由于 ref_count 减为零导致被析构的 shared_ptrinvalid,其 .get() 方法返回的指针不是 nullptr,可能为任意悬空的指针。不要用 .get() 是否为 nullptr 来判断一个 shared_ptr 是否 valid,需要使用 weak_ptr 来判断。


综上所述,尽可能多的使用 unique_ptr(快,小), 在不确定owner并需要共享的时候,使用shared_ptr,在环形引用时考虑 weak_ptr(不会增加只针的引用计数)

如果是新建一个对象则立即赋值给 unique_ptr 或者 shared_ptr,如果函数返回new了之后普通指针时,也立即将普通指针赋值给智能指针,以保证自动内存管理

智能指针对象 的形式保存在container中(如:a vector of shared_ptr<T>而不是 a vector of shared_ptr<T&>),而不是指针或者引用;如果不确定函数内部会不会修改该智能指针,则也以 传值 的方式传递智能指针,函数返回智能指针 对象(而不是 引用 和 指针)等。

智能指针具有多态(跟普通指针一样),shared_ptr 还可以与不完全类型一起使用(前置声明)。

关于 shared_ptr 多线程安全:

对 shared_ptr 进行读写都不是原子操作,因此多线程读写 shared_ptr 不安全
shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be “read” (accessed using only const operations) simultaneously by multiple threads. Different shared_ptr instances can be “written to” (accessed using mutable operations such as operator= or reset) simultaneously by multiple threads (even when these instances are copies, and share the same reference count underneath.)

Any other simultaneous accesses result in undefined behavior.

Examples:

shared_ptr<int> p(new int(42));

//--- Example 1 ---

// thread A
shared_ptr<int> p2(p); // reads p

// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe

//--- Example 2 ---

// thread A
p.reset(new int(1912)); // writes p

// thread B
p2.reset(); // OK, writes p2

//--- Example 3 ---

// thread A
p = p3; // reads p3, writes p

// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write

//--- Example 4 ---

// thread A
p3 = p2; // reads p2, writes p3

// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"

//--- Example 5 ---

// thread A
p3.reset(new int(1));

// thread B
p3.reset(new int(2)); // undefined, multiple writes

那么,函数使用 shared_ptr 作为函数的参数的时候,是该传递引用呢,还是传值呢?

That depends on what you want. Should the callee share ownership of the object? Then it needs its own copy of the shared_ptr. So pass it by value.
If a function simply needs to access an object owned by the caller, go ahead and pass by (const) reference, to avoid the overhead of copying the shared_ptr.
The best practice in C++ is always to have clearly defined ownership semantics for your objects. There is no universal “always do this” to replace actual thought.
If you always pass shared pointers by value, it gets costly (because they’re a lot more expensive to copy than a raw pointer). If you never do it, then there’s no point in using a shared pointer in the first place.
Copy the shared pointer when a new function or object needs to share ownership of the pointee.

总结:所以说,如果你不会在该函数中对 shared_ptr 自身做任何修改(reset, swap, operator=, make_shared ...),而只是想通过该 shared_ptr 获取一些资源( operator*, operator-> ...), 那么可以传递引用(最好是常量引用)。否则如果你不确定该函数是否会修改shared_ptr自身,那么就传值吧,因为这样不会影响被传递进来的那个智能指针。 So so so,综上所述,当智能指针作为函数参数时,尽可能用常量引用(即 const shared_ptr<T> &

关于 何时 用 shared_ptr, unique_ptr 和 raw pointer, see link:
http://stackoverflow.com/questions/8706192/which-kind-of-pointer-do-i-use-when


std::move 和 右值引用:

https://mbevin.wordpress.com/2012/11/20/move-semantics/

move 与 std 模板容器的应用:

一般来说使用 vector 或者其他容器都会 suffer copy overhead,因为 push_back 会调用复制构造函数将对象副本保存在容器中。
但是可以使用c++11容器的新方法消除临时对象: emplace_front、emplace和emplace_back
// 例如:
vector<Foo> vf;
vf.push_back(Foo("xyz",123));

这样做首先是构造一个临时的局部Foo对象,然后再通过拷贝的方式把这个临时对象拷贝到容器内(如果Foo不支持move操作的话)。但如果我们使用emplace函数,我们可以这样:

 vf.emplace_back("xyz",123);

其结果是直接在容器内的内存空间中创建对象,而不必要浪费再构建临时变量和进行拷贝了,因此emplace比传统的push_back函数具有更高的效率(在Foo不支持move操作的情况下)。

see link: https://isocpp.org/wiki/faq/containers


智能指针基类,派生类的转换: dynamic_pointer_cast vs static_pointer_cast

dynamic_pointer_cast需要至少一个虚函数存在。失败则返回null指针:
  // The shared_ptr knows how to upcast its
  // inner pointer.
  shared_ptr< base_type > sp_base(sp_derived);  // 一般都是以这种形式新建智能指针:新建基类智能指针 shared_ptr< base_type > 而不是派生类智能指针 shared_ptr< derived_type >

  // You can static-downcast the inner pointer.
  shared_ptr< derived_type >
	sp_derived2(static_pointer_cast< derived_type >( sp_base));  // 很少定义 shared_ptr< derived_type >,最好别这么做,因为其不能传递给以 shared_ptr< base_type > 作为参数的接口
	
  // dynamic_pointer_cast works at least one virtual func exist.
	shared_ptr< derived_type_other >
	sp_derived_other(
	  dynamic_pointer_cast< derived_type_other >( sp_base));
	BOOST_ASSERT( sp_derived_other);

lock_guard,正如 effective c++ 中所强调,用对象来管理资源:

std::unique_lock, std::lock_guard 和 boost::scope_lock 采用对象来管理mutex
std::mutex mtx
int somefunc()
{
	std::lock_guard<std::mutex> lck(mtx)  // lock
	//do something
	
	// lck对象被析构,自动unlock; 复杂点还有的 std::unique_lock
}

final 和 override 关键字

如果需要重写一个virtual函数,显示提供override关键字能够避免意外的覆盖或者隐藏基类的函数: override明确地表示一个函数是对基类中一个虚函数的重载。更重要的是,它会检查基类虚函数和派生类中重载函数的签名不匹配问题。如果签名不匹配,编译器会发出错误信息。

struct B
{
    virtual void f() {}
};
 
struct D : B
{
    void f() {}
};

开发 D 的程序员真的想重写B::f函数吗?还是说,他只是不小心写了个与父类同名的函数,却在不经意间导致了覆盖?为了避免这种错误,C++ 11 引入了override关键字。于是,我们会发现,下面的一段代码是会出错的:

struct B
{
    virtual void f(int) {}
};
 
struct D : B
{
    virtual void f(int) override {} // OK
    virtual void f(double) override {} // Error,加上override关键字可以防止基类的函数被隐藏或覆盖。
};

当处理到D::f()声明时,编译器会 在一个基类查找与之匹配的虚函数。回想一下,“匹配”在上下文中的意思是:

  • 相同的函数名
  • 在声明该函数的第一个基类中的一个虚拟说明符
  • 基类函数和派生类重载函数具有相同的参数列表、返回类型(一种情况例外,返回基类与派生类的指针或引用的区别)、cv资格等。

final则表示该类不能被当做基类。如果修改函数则表示该函数不能在派生来中被重写。

struct B final { };	// 定义为final类
struct D : B { }; // 错误!不能从 final 类继承!
struct B
{
    virtual void f() final {} // final 函数
};

struct D : B
{
    virtual void f() {}  //错误!不能在D中重写B::f,因为B::f被声明为 final !
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值