玩命加载中 . . .

12.1-动态内存与智能指针


shared_ptr允许多个指针指向同一个对象,unique_ptr则独占所指向的对象,weak_ptr是一种弱引用,指向shared_ptr所管理的对象

12.1.1 shared_ptr类

需要指定想要创建的对象的类型

shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<string> p4 = make_shared<string>(10, '9');
shared_ptr<int> p5 = make_shared<int>();

make_shared用其参数来构造给定类型的对象

也可以用auto

auto p6 = make_shared<vector<string>>();

shared_ptr的拷贝和赋值

auto p = make_shared<int>(42);
auto q(p);
cout << p.use_count() << endl;  // 2

当进行拷贝或赋值时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象

我们可以认为每个shared_ptr都有一个关联的计数器,称为引用计数,拷贝一个shared_ptr时,计数器会递增

一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象

auto r = make_shared<int>(42);
r = q;

r赋值后,r指向另一个地址,这样会递增q指向的对象的引用计数,递减r原来指向对象的引用计数,如果r原来的对象已没有引用者,会自动释放

定义StrBlob

class StrBlob {
public:
    typedef vector<string>::size_type size_type;
    StrBlob(): data(make_shared<vector<string>>()) {}
    StrBlob(initializer_list<string> il): data(make_shared<vector<string>>(il)) {}
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const string& t) { data->push_back(t); }
    void pop_back();
    string& front();
    string& back();
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

StrBlob中有个共享指针的成员,指针指向的是一个动态分配的vector,这样就可以在多个对象间共享这个vector

check函数确保访问不会越界

void StrBlob::check(size_type i, const string& msg) const {
    if (i >= data->size()) 
        throw out_of_range(msg);
}

string& StrBlob::front() {
    check(0, "front on empty StrBlob");
    return data->front();
}

string& StrBlob::back() {
    check(0, "back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back() {
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

12.1.3 shared_ptr和new结合使用

可以用new返回的指针来初始化智能指针

shared_ptr<double> p2(new int(42));

接受指针参数的智能指针构造函数是explicit的,必须使用直接初始化形式

shared_ptr<int> p1 = new int(1024); // 错误,不能使用拷贝初始化
shared_ptr<int> p2(new int(1024));  // 正确,使用直接初始化

下面的函数接受一个按值传递的智能指针

void process(shared_ptr<int> ptr) {
    // 使用ptr
}   // ptr离开作用域,被销毁

shared_ptr<int> p(new int(42));
process(p); // 拷贝p会增加引用计数,在process中引用计数为2
int i = *p; // 正确:引用计数为1

按值传递时,实参会被拷贝到ptr中,使得对象的引用计数增加为2,当函数结束时,ptr的引用计数会减1,但不会变成0,所以,指向的内存不会被释放

但是,不能传递一个普通指针或临时的shared_ptr,因为函数结束时,智能指针指向对象的引用计数会变成0,将内存释放,此时普通指针就变成空悬指针

int *x(new int(42));    // x是一个普通指针
process(x);     // 错误,不能将int*转换成shared_ptr<int>
process(shared_ptr<int>(x));    // 合法,但内存会被释放
int j = *x;     // x是一个空悬指针

get会返回一个内置指针,使用场景是:我们需要向不能使用智能指针的代码传递一个内置指针,使用get返回的指针的代码不能delete此指针,如果这边delete了,智能指针那边就变成空悬指针了

shared_ptr<int> p(new int(42));
int *q = p.get();   // 正确,但不能让q被delete
{
    shared_ptr<int>(q); // 用q又创建了一个智能指针,此时p和q指向同一块内存
}   // 代码块结束时,内存就被释放了
int foo = *p;   // 此时p变成空悬指针了

get用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get,特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值

注意

  • 不使用相同的内置指针值初始化多个智能指针
  • deleteget返回的指针
  • 不使用get初始化或reset另一个智能指针
  • 如果使用get返回的指针,记住当最后一个对应的智能指针销毁后,指针就变为无效了
  • 如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

12.1.5 unique_ptr

一个unique_ptr拥有它指向的对象,某个时刻只能有一个unique_ptr指向一个给定对象,需要绑定到一个new返回的指针上,必须使用直接初始化方式

unique_ptr<double> p1;  // 可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(42));    // 直接初始化

不支持普通的拷贝或赋值操作

unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1);  // 错误,不支持拷贝
unique_ptr<string> p3;
p3 = p2;    // 错误,不支持赋值

可以使用releasereset将指针的所有权从一个unique_ptr转移给另一个unique_ptr

unique_ptr<string> p2(p1.release());    // 将所有权从p1转移给p2,release将p1置为空
unique_ptr<string> p3(new string("world"));
p2.reset(p3.release()); // reset释放p2原来指向的内存,并将p3指向的对象的所有权转移给p2

p2接管了p1的对象后,调用reset会先释放p2指向的内存,然后将p3对指针的所有权转移给p2,并将p3置为空

调用release会放弃对指针的控制权,并返回指针,所以应该有人来接管这个指针,并负责后续的资源释放

p2.release();   // 错误,p2不会释放内存,而且我们失去了指针
auto p = p2.release();  // 正确,记得delete p

可以拷贝或赋值一个将要被销毁的unique_ptr,比如从函数返回一个unique_ptr

unique_ptr<int> clone(int p) {
    return unique_ptr<int>(new int(p));
}

还可以返回一个局部对象的拷贝

unique_ptr<int> clone(int p) {
    unique_ptr<int> ret(new int(p));
    return ret;
}

指定删除器时,必须在尖括号中提供删除器的类型

unique_ptr<objT, delT> p(new objT, fcn);

p指向一个类型为objT的对象,并使用一个类型为delT的对象释放objT对象,它会调用一个名为fcndelT类型对象

12.1.6 weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象

当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它

auto p = make_shared<int>(42);
weak_ptr<int> wp(p);    // wp弱共享p,p的引用计数未改变

由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock,此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr,否则返回空的shared_ptr

if (shared_ptr<int> np = wp.lock()) {
    // 只要np不为空则条件成立
    // 使用np访问共享对象
}

定义StrBlobPtr

class StrBlobPtr;

class StrBlob {
public:
    friend class StrBlobPtr;
    StrBlobPtr begin();
    StrBlobPtr end();
public:
    typedef vector<string>::size_type size_type;
    StrBlob(): data(make_shared<vector<string>>()) {}
    StrBlob(initializer_list<string> il): data(make_shared<vector<string>>(il)) {}
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const string& t) { data->push_back(t); }
    void pop_back();
    string& front();
    string& back();
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

void StrBlob::check(size_type i, const string& msg) const {
    if (i >= data->size()) 
        throw out_of_range(msg);
}

string& StrBlob::front() {
    check(0, "front on empty StrBlob");
    return data->front();
}

string& StrBlob::back() {
    check(0, "back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back() {
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

class StrBlobPtr {
public:
    friend bool operator==(const StrBlobPtr&, const StrBlobPtr&);
    friend bool operator!=(const StrBlobPtr&, const StrBlobPtr&);
    StrBlobPtr(): curr(0) {}
    StrBlobPtr(StrBlob& a, size_t sz = 0): wptr(a.data),curr(sz) {}
    string& deref() const;
    StrBlobPtr& operator++();
private:
    shared_ptr<vector<string>> check(size_t, const string&) const;
    weak_ptr<vector<string>> wptr;
    size_t curr;
};

shared_ptr<vector<string>> 
StrBlobPtr::check(size_t i, const string& msg) const {
    auto ret = wptr.lock();
    if (!ret) {
        throw runtime_error("unbound StrBlobPtr");
    }
    if (i >= ret->size()) {
        throw out_of_range(msg);
    }
    return ret;
}

string& StrBlobPtr::deref() const {
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::operator++() {
    check(curr, "increment past end of StrBlobPtr");
    ++curr;
    return *this;
}

bool operator==(const StrBlobPtr& lhs, const StrBlobPtr& rhs) {
    return lhs.curr == rhs.curr;
}

bool operator!=(const StrBlobPtr& lhs, const StrBlobPtr& rhs) {
    return !(lhs == rhs);
}

// 这两个函数要放到StrBlobPtr定义之后
StrBlobPtr StrBlob::begin() { 
    return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end() {
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

int main() {
    StrBlob s1{"kavin", "jack", "lisa"};
    for (auto it = s1.begin(); it != s1.end(); ++it) {
        cout << it.deref() << endl;
    }
    return 0;
}
// kavin
// jack
// lisa

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录