65.9K
CodeProject 正在变化。 阅读更多。
Home

智能指针的一些想法(第 2 部分)。size == sizeof(std::shared_ptr)/2

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (2投票s)

2012年6月3日

CPOL

1分钟阅读

viewsIcon

29745

downloadIcon

133

嵌入式引用计数资源管理

引言

对于没有继承关系的原始类型,例如 charshortintfloat...,我们可以进一步优化,使得 sizeof (智能指针) == sizeof (原始指针)

Win32 中的示例

sizeof(int*) == 4
sizeof(std::shared_ptr<int>) == 8
sizeof(boost::shared_array<int>) == 8
sizeof(smart pointer in this section) == 4

这比 std::shared_ptrboost::shared_array 更经济高效,速度更快。

背景

在 C++ 中,如果进行指针转换,继承关系会改变指针的值。尝试以下简单的代码

struct base
{
};

struct der: base
{
	virtual void fff()
	{
	}
};

int main()
{
    der obj;
    base* p = &obj;

    if(p == &obj)
    {
        printf("pointer is equal\n");
    }

    if(unsigned(p) != (unsigned)&obj)
    {
	printf("value is not equal\n");
    }
}

程序的输出是“指针相等”和“值不相等”。

这就是为什么在 Win32 系统中 sizeof(std::shared_ptr<>) == 8。它无法从对象指针值(由于继承)中检索引用计数位置。因此,它需要在内部存储一个额外的指针。

解决方案

有一种例外情况。对于 C++ 内置数据类型,不可能存在继承关系。这种限制为我们提供了一个机会来最小化智能指针。

内置对象内存分配

引用计数器 空白区域,以确保内置对象的对齐 内置对象

内置数组对象内存分配

引用计数器 空白区域,以确保内置对象的对齐 内置对象1 内置对象2 ...

我们直接使用重载的 new 运算符在分配期间将引用计数器嵌入到对象堆中。引用计数器和对象(们)共享同一个堆,这将大大提高效率并减少资源消耗。想想 boost::make_shared

一些关键代码

template<typename T, size_t align>
struct NativeCore
{
    NativeCore(T* p, size_t align): m_ptr(p), m_align(align) { }
    T* m_ptr; size_t m_align; 
};
template<typename T, void*(*allocator)(size_t), void(*cleaner)(void*), size_t align> 
inline NativeCore<T, align> NativeAllocMemOnly(size_t total)
{ 
    size_t remain = sizeof(long)%align;
    if(0 == remain)
    {
        long* p = reinterpret_cast<long*>(allocator(sizeof(T)*total + sizeof(long)));
        *p = 1;
        ::new(cleaner, p+1) T[total]; //constructor for extended uses. 
                                      //built-in types can ignore
        return NativeCore<T, align>(reinterpret_cast<T*>(p+1), align);
    }
    else
    {
        char* p = reinterpret_cast<char*>(allocator(sizeof(T)*total + 
                              sizeof(long) + align - remain));
        *reinterpret_cast<long*>(p) = 1; ::new(cleaner, p+sizeof(long) + 
         align - remain) T[total]; //constructor for extended uses. build-in types can ignore
        return NativeCore<T, align>(reinterpret_cast<T*>(p+sizeof(long) + 
                                    align - remain), align);
    }
}
template<typename T, size_t alignment>
inline NativeCore<T, alignment> NativeAlloc(size_t total)
{
    StaticAssert<!IsClassType<T>::m_value>(); //no class type
    return NativeAllocMemOnly<T, operator new, operator delete, alignment>(total);
}

测试代码
内置类型的引用计数管理。不再需要显式的 free 或 delete。

#include "stdafx.h" 
int main() 
{
    using namespace std;
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);

    RefNative<int>::Normal n = NativeAlloc<int>(2); //two int
    n[0] = 1;
    n[1] = 2;
    int* pn = n;

    printf("Current ref-count before push_back = %d\n", n.GetRefCount());
    std::vector<RefNative<int>::Normal> vec;
    vec.push_back(n);    //auto InterlockedIncrement to make thread-safe
    printf("Current ref-count after push_back = %d\n", n.GetRefCount());

    printf("sizeof(RefNative<int>::Normal) = %d\n", sizeof(RefNative<int>::Normal));

    //demo aligned operation
    RefNative<double, 64>::
    Aligned na = NativeAllocAligned<double, 64>(1); //allocate one 64 bytes aligned double
    double* pna = na; pna[0] = 3.5;
    printf("sizeof(RefNative<int>::Aligned) = %d\n", sizeof(RefNative<int>::Aligned));
    if(unsigned(pna)%64 == 0)
    {
       puts("it is properly 64 bytes aligned\n");
    }
    return 0; 
}

关注点

如果数组对象(不仅仅是内置类型)和引用计数器可以共享同一个堆,我们将产生比 shared_ptrboost::shared_array 更经济的智能指针。
我们将在第 3 部分中进行相应的测试。

历史

  • 2006:创建
  • 2012 年 6 月 3 日:文章发布
© . All rights reserved.