请支持原创~~
所谓智能指针,可以从字面上理解为“智能”的指针。具体来讲,智能指针和普通指针的用法是相似的,不同之处在于,智能指针可以在适当时机自动释放分配的内存。也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。由此可见,C++ 也逐渐开始支持垃圾回收机制了,尽管目前支持程度还有限。
c++11 中发布了shared_ptr、unique_ptr、weak_ptr 用以资源的管理,都是定义在memory 这个头文件中。
本文主要分析unique_ptr 相关属性和使用。
#include <memory>
constexpr unique_ptr() noexcept;
explicit unique_ptr(pointer p) noexcept;
unique_ptr(pointer p, see below d1) noexcept;
unique_ptr(pointer p, see below d2) noexcept;
unique_ptr(unique_ptr&& u) noexcept;
unique_ptr(nullptr_t) noexcept : unique_ptr() { }
template <class U, class E>
unique_ptr(unique_ptr<U, E>&& u) noexcept;
template <class U>
unique_ptr(auto_ptr<U>&& u) noexcept; // removed in C++17
构造函数比较多啊,但是相比shared_ptr 少了很多,unique_ptr 删除了拷贝构造函数,保留着移动构造函数。
explicit unique_ptr(pointer __p) noexcept : __ptr_(__p) {}
unique_ptr(unique_ptr&& __u) noexcept
: __ptr_(__u.release(), _VSTD::forward<deleter_type>(__u.get_deleter())) {
}
相对于shared_ptr,unique_ptr 中有对象的指针__ptr_,但是没有了__cntrl_
另外,从移动构造函数中确认,对象是从一个unique_ptr 转移到新的unique_ptr 实例中。
下面举例unique_ptr 通常的创建方式:
std::unique_ptr<int> p1; //不传入任何实参
std::unique_ptr<int> p2(nullptr); //传入空指针 nullptr
std::unique_ptr<int> p3(new int(10)); //指定指针为参数
std::unique_ptr<int> p4(p3); //错误,unique_ptr没有提供拷贝构造函数
std::unique_ptr<int> p5(std::move(p4)); //或者 std::unique_ptr<int> p5 = std::move(p4);
unique_ptr& operator=(unique_ptr&& u) noexcept;
template <class U, class E> unique_ptr& operator=(unique_ptr<U, E>&& u) noexcept;
unique_ptr& operator=(nullptr_t) noexcept;
pointer release() noexcept;
void reset(pointer p = pointer()) noexcept;
void swap(unique_ptr& u) noexcept;
typename add_lvalue_reference<T>::type operator*() const;
pointer operator->() const noexcept;
pointer get() const noexcept;
deleter_type& get_deleter() noexcept;
const deleter_type& get_deleter() const noexcept;
explicit operator bool() const noexcept;
默认情况下,unique_ptr 指针采用 std::default_delete 方法释放堆内存。当然,我们也可以自定义符合实际场景的释放规则。值得一提的是,和 shared_ptr 指针不同,为 unique_ptr 自定义释放规则,只能采用函数对象的方式。例如:
//自定义的释放规则
struct myDel
{
void operator()(int *p) {
delete p;
}
};
std::unique_ptr<int, myDel> p6(new int);
//std::unique_ptr<int, myDel> p6(new int, myDel());
对于unique_ptr 的成员函数总结如下:
成员函数名 | 功 能 |
operator*() | 获取当前 unique_ptr 指针指向的数据。 |
operator->() | 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。 |
operator =() | 重载了 = 赋值号,从而可以将 nullptr 或者一个右值 unique_ptr 指针直接赋值给当前同类型的 unique_ptr 指针。 |
operator []() | 重载了 [] 运算符,当 unique_ptr 指针指向一个数组时,可以直接通过 [] 获取指定下标位置处的数据。 |
get() | 获取当前 unique_ptr 指针内部包含的普通指针。 |
get_deleter() | 获取当前 unique_ptr 指针释放堆内存空间所用的规则。 |
operator bool() | unique_ptr 指针可直接作为 if 语句的判断条件,以判断该指针是否为空,如果为空,则为 false;反之为 true。 |
release() | 释放当前 unique_ptr 指针对所指堆内存的所有权,但该存储空间并不会被销毁。 |
reset(p) | 其中 p 表示一个普通指针,如果 p 为 nullptr,则当前 unique_ptr 也变成空指针;反之,则该函数会释放当前 unique_ptr 指针指向的堆内存(如果有),然后获取 p 所指堆内存的所有权(p 为 nullptr)。 |
swap(x) | 交换当前 unique_ptr 指针和同类型的 x 指针。 |
除此之外,C++11标准还支持同类型的 unique_ptr 指针之间,以及 unique_ptr 和 nullptr 之间,做 ==,!=,,>= 运算。
注意:
#include <cassert>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <memory>
#include <stdexcept>
// helper class for runtime polymorphism demo below
struct B
{
virtual ~B() = default;
virtual void bar() { std::cout << "B::bar\n"; }
};
struct D : B
{
D() { std::cout << "D::D\n"; }
~D() { std::cout << "D::~D\n"; }
void bar() override { std::cout << "D::bar\n"; }
};
// a function consuming a unique_ptr can take it by value or by rvalue reference
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
p->bar();
return p;
}
// helper function for the custom deleter demo below
void close_file(std::FILE* fp)
{
std::fclose(fp);
}
// unique_ptr-based linked list demo
struct List
{
struct Node
{
int data;
std::unique_ptr<Node> next;
};
std::unique_ptr<Node> head;
~List()
{
// destroy list nodes sequentially in a loop, the default destructor
// would have invoked its `next`'s destructor recursively, which would
// cause stack overflow for sufficiently large lists.
while (head)
head = std::move(head->next);
}
void push(int data)
{
head = std::unique_ptr<Node>(new Node{data, std::move(head)});
}
};
int main()
{
std::cout << "1) Unique ownership semantics demo\n";
{
// Create a (uniquely owned) resource
std::unique_ptr p = std::make_unique<D>();
// Transfer ownership to `pass_through`,
// which in turn transfers ownership back through the return value
std::unique_ptr q = pass_through(std::move(p));
// `p` is now in a moved-from 'empty' state, equal to `nullptr`
assert(!p);
}
std::cout << "\n" "2) Runtime polymorphism demo\n";
{
// Create a derived resource and point to it via base type
std::unique_ptr<B> p = std::make_unique<D>();
// Dynamic dispatch works as expected
p->bar();
}
std::cout << "\n" "3) Custom deleter demo\n";
std::ofstream("demo.txt") << 'x'; // prepare the file to read
{
using unique_file_t = std::unique_ptr<std::FILE, decltype(&close_file)>;
unique_file_t fp(std::fopen("demo.txt", "r"), &close_file);
if (fp)
std::cout << char(std::fgetc(fp.get())) << '\n';
} // `close_file()` called here (if `fp` is not null)
std::cout << "\n" "4) Custom lambda-expression deleter and exception safety demo\n";
try
{
std::unique_ptr<D, void(*)(D*)> p(new D, [](D* ptr)
{
std::cout << "destroying from a custom deleter...\n";
delete ptr;
});
throw std::runtime_error(""); // `p` would leak here if it were instead a plain pointer
}
catch (const std::exception&) { std::cout << "Caught exception\n"; }
std::cout << "\n" "5) Array form of unique_ptr demo\n";
{
std::unique_ptr<D[]> p(new D[3]);
} // `D::~D()` is called 3 times
std::cout << "\n" "6) Linked list demo\n";
{
List wall;
for (int beer = 0; beer != 1'000'000; ++beer)
wall.push(beer);
std::cout << "1'000'000 bottles of beer on the wall...\n";
} // destroys all the beers
}
运行结果:
1) Unique ownership semantics demo
D::D
D::bar
D::~D
2) Runtime polymorphism demo
D::D
D::bar
D::~D
3) Custom deleter demo
x
4) Custom lambda-expression deleter and exception safety demo
D::D
destroying from a custom deleter...
D::~D
Caught exception
5) Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D
6) Linked list demo
1'000'000 bottles of beer on the wall...
template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args );
template< class T >
unique_ptr<T> make_unique( std::size_t size );
template< class T, class... Args >
/* unspecified */ make_unique( Args&&... args ) = delete;
与shared_ptr 不同,在c++11 发布的时候并没有make_unique,其实也可以想到,make_shared 是为了避开两次创建的过程,因为shared_ptr 中有计数控制。而unique_ptr 中并没有类似的,所以make_unique 可有可无。
实现大致如下:
template<class T, class... Args>
std::enable_if_t<!std::is_array<T>::value, std::unique_ptr<T>>
make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
个人猜测在c++14 中提出make_unique 是为了unique_ptr 数组,所以就一并发布了。
下面来看个例子帮助理解:
#include <iostream>
#include <iomanip>
#include <memory>
struct Vec3
{
int x, y, z;
// following constructor is no longer needed since C++20
Vec3(int x = 0, int y = 0, int z = 0) noexcept : x(x), y(y), z(z) { }
friend std::ostream& operator<<(std::ostream& os, const Vec3& v) {
return os << "{ x=" << v.x << ", y=" << v.y << ", z=" << v.z << " }";
}
};
int main()
{
// Use the default constructor.
std::unique_ptr<Vec3> v1 = std::make_unique<Vec3>();
// Use the constructor that matches these arguments
std::unique_ptr<Vec3> v2 = std::make_unique<Vec3>(0,1,2);
// Create a unique_ptr to an array of 5 elements
std::unique_ptr<Vec3[]> v3 = std::make_unique<Vec3[]>(5);
std::cout << "make_unique<Vec3>(): " << *v1 << '\n'
<< "make_unique<Vec3>(0,1,2): " << *v2 << '\n'
<< "make_unique<Vec3[]>(5): ";
for (int i = 0; i < 5; i++) {
std::cout << std::setw(i ? 30 : 0) << v3[i] << '\n';
}
}
运行结果:
make_unique<Vec3>(): { x=0, y=0, z=0 }
make_unique<Vec3>(0,1,2): { x=0, y=1, z=2 }
make_unique<Vec3[]>(5): { x=0, y=0, z=0 }
{ x=0, y=0, z=0 }
{ x=0, y=0, z=0 }
{ x=0, y=0, z=0 }
{ x=0, y=0, z=0 }