zl程序教程

您现在的位置是:首页 >  其他

当前栏目

C++|内存管理|智能指针别名构造函数

2023-03-15 22:02:26 时间

本文参考ISO文档

在C++编程实践中, 几乎所有人都或多或少地使用过智能指针.但是在智能指针的一个小角落, Alias Constructor大概不为多少人所知.


根据stackoverflow上的一个讨论, Alias Constructor被描述如下

Additionally, shared_ptr objects can share ownership over a pointer while at the same time pointing to another object. This ability is known as aliasing (see constructors), and is commonly used to point to member objects while owning the object they belong to. Because of this, a shared_ptr may relate to two pointers:

  • A stored pointer, which is the pointer it is said to point to, and the one it dereferences with operator*.
  • An owned pointer (possibly shared), which is the pointer the ownership group is in charge of deleting at some point, and for which it counts as a use.

Generally, the stored pointer and the owned pointer refer to the same object, but alias shared_ptr objects (those constructed with the alias constructor and their copies) may refer to different objects.


什么意思呢,在谈Alias Constructor之前, 我们先简略了解一下shared_ptr实现机制, shared_ptr由两个指针构成, 一个指向控制块, 一个指向存储的指针. 控制块会控制其存储指针的生存期. 但是, 重点来了, 控制块中存储的指针未必是sharedptr存储的指针.

事实上, shared_ptr中存储的控制块指针仅仅只是一个非模板基类, 而存储指针/删除器/分配器等特征都通过指向派生出的模板子类(多态)而体现. 这里后面的特征通过了Type erasure 而逃过了编译器的类型检查.

内部控制块的模板参数和外部智能指针的模板参数毫无干系, 因此控制块指针和存储指针本质上是完全独立的.


那么,为什么C++需要用如此复杂的方式去实现呢?

一个原因是设计者认为删除器/分配器等非必需的工具不应该影响到指针的类型

另一个原因就要讲到这里的Alias了.

Alias,化名, 在这里意为两者具有相同的生命周期.

其语法为shared_ptr<element> ptr(Tptr, element*)

在这里尽管Tptr具有模板参数为T, 但是ptr(模板参数为element)仍旧可以共享其控制块.

在Stackoverflow上有这样一个例子.

struct Bar { 
    // some data that we want to point to
};

struct Foo {
    Bar bar;
};

shared_ptr<Foo> f = make_shared<Foo>(some, args, here);
shared_ptr<Bar> specific_data(f, &f->bar);

// ref count of the object pointed to by f is 2
f.reset();

// the Foo still exists (ref cnt == 1)
// so our Bar pointer is still valid, and we can use it for stuff
some_func_that_takes_bar(specific_data);

对象的成员和对象本身,显然的确应该共享生存期,但有人可能会问了, 为什么不用->调用成员呢, 似乎这并非刚需.


在思考很久之后, 不同类型对象共享生存期的情况映入了我的脑中.

struct Base1 { int base1=0; };   
struct Base2 { int base2=0; };   
struct A:Base1,Base2{};    

int main()
{
    auto ptr = new A;     

        shared_ptr<Base1>ptr1 ((Base1*)ptr);    

        //shared_ptr<Base2>ptr2 ((Base2*)ptr);    

        //内存泄漏,ptr2已经被释放:-572662307end crash     

        shared_ptr<Base2>ptr2(ptr1, (Base2*)(ptr));    

        //化名,ptr2未被释放:0end    

        ptr1.reset();    

        cout << ptr2->base2;    

        cout << "end";    

    return 0;    

}    

在C++中,一种特殊的情况在于多继承, 当我们面向接口(类型)编程时, 如果我们使用接口指针去操纵对象,就不得不遇到不同接口的智能指针指向同一个对象了. 一旦我们没有使用Alias, 由于不同类型指针无法共享控制块, 这势必会产生内存泄漏, 而通过Alias, 我们则完美地避免了这一情况.