c++ - 如何在连续内存中多态地存储和访问来自同一继承层次结构的不同类型?

标签 c++ memory-management polymorphism c++14

对于多态性,通常的方法是使用 std::vector<base*> .但是,我必须自己提供地址,即无论我是否使用std::unique_ptr<>,都要自己管理内存。或原始指针。

我想要一个 polymorphic_storage<base>接受任何继承自 base 的类型的类型.我还希望将类型存储在连续内存中,以便更快地遍历和解决与缓存相关的问题。

但是,有一个相当大的问题:在存储级别没有类型信息的情况下,必须在调整大小时调用正确的移动/复制操作。

功能要求:

  • 任何从基类继承的类型都可以添加到存储中;没有固定的继承层次结构。
  • 继承类型必须在存储类型内部正确对齐。
  • 必须调用正确的移动和复制操作,因为我不处理 POD 类型。

我可以使用什么机制来实现这一点?


虽然我提供了答案,但我欢迎任何人发布他们的解决方案。

最佳答案

现在支持对齐。

演示:http://coliru.stacked-crooked.com/a/c304d2b6a475d70c


此答案侧重于解决问题中要求的三个功能。

  • 不使用静态内存,因为如果将新类型添加到继承层次结构并且该新类型超出静态限制,则会导致代码中断更改。
  • 存储中的所有类型都正确对齐。
  • 发生重新分配时会调用正确的移动/复制构造函数。

它用 fstrict-aliasing 编译,所以不要太害怕reinterpret_cast<>()用法。


handle_base类型有 void*名为 src_ 的数据成员,它指向一些值。它有两个作用于 src_ 的成员函数.

void transfer( void* dst, std::size_t& out_size )

使用placement-new 来移动或复制src_ 指向的值。在 dst , 然后设置 src_dst .它还将类型的大小(以字节为单位)添加到 out_size引用论据;这对于正确对齐类型很有用。

void* src()

返回指针 src_ .

handle_base.h

namespace gut
{
    template<class T> class handle;

    class handle_base
    {
    public:
        virtual ~handle_base() = default;

        handle_base() = default;
        handle_base( handle_base&& ) = default;
        handle_base( handle_base const& ) = default;
        handle_base& operator=( handle_base&& ) = default;
        handle_base& operator=( handle_base const& ) = default;

        void* src() const noexcept
        {
            return src_;
        }

        virtual void transfer( void* dst, std::size_t& out_size ) = 0;
        virtual void destroy() = 0;

    protected:
        handle_base( void* src ) noexcept
            : src_{ src }
        {}

        void* src_;
    };
}

接下来,我创建 handle<T>继承自 handle_base 的类型为了提供正确的移动/复制操作。此级别提供类型信息;这可以实现从正确对齐到正确移动/复制操作的一切。

void transfer( void* dst, std::size_t& out_size )

该函数将负责选择是使用移动构造函数还是复制构造函数。如果可用,将始终选择移动构造函数。它计算对齐所需的任何填充,将值传输到 src_dst + padding并增加 out_size引用参数的大小和填充。

handle.h

namespace gut
{
    template<class T>
    static std::size_t calculate_padding( void* p ) noexcept
    {
        std::size_t r{ reinterpret_cast<std::uintptr_t>( p ) % alignof( T ) };
        return r == 0 ? 0 : alignof( T ) - r;
    }

    template <class T>
    class handle final : public handle_base
    {
    public:
        using byte = unsigned char;

        static_assert( sizeof( void* ) == sizeof( T* ),
            "incompatible pointer sizes" );

        static constexpr std::integral_constant
        <
            bool, std::is_move_constructible<T>::value
        > is_moveable{};

        handle( T* src ) noexcept
            : handle_base( src )
        {}

        handle( handle&& ) = default;
        handle( handle const& ) = default;
        handle& operator=( handle&& ) = default;
        handle& operator=( handle const& ) = default;

        void transfer( std::true_type, void* dst )
        noexcept( std::is_nothrow_move_assignable<T>::value )
        {
            src_ = ::new ( dst ) T{ std::move( *reinterpret_cast<T*>( src_ ) ) };
        }

        void transfer( std::false_type, void* dst )
        noexcept( std::is_nothrow_copy_assignable<T>::value )
        {
            src_ = ::new ( dst ) T{ *reinterpret_cast<T*>( src_ ) };
        }

        virtual void transfer( void* dst, std::size_t& out_size )
        noexcept( noexcept(
            std::declval<handle>().transfer( is_moveable, dst ) ) ) override
        {
            std::size_t padding{ gut::calculate_padding<T>( dst ) };
            transfer( is_moveable, reinterpret_cast<byte*>( dst ) + padding );
            out_size += sizeof( T ) + padding;
        }

        virtual void destroy()
        noexcept( std::is_nothrow_destructible<T>::value )
        {
            reinterpret_cast<T*>( src_ )->~T();
            src_ = nullptr;
        }
    };
}

因为我知道 sizeof( handle_base ) == sizeof( handle<T> )对于任何 T ,我创建一个polymorphic_handle类型作为一个额外的间接使用方便。此类型可以容纳任何 handle<T>和过载 operator->()这样它就可以充当任何句柄的通用句柄。

polymorphic_handle.h

namespace gut
{
    class polymorphic_handle
    {
    public:
        using value_type = gut::handle_base;
        using pointer = value_type*;
        using const_pointer = value_type const*;

        template<class T>
        polymorphic_handle( gut::handle<T> h ) noexcept
        {
            ::new ( &h_ ) gut::handle<T>{ h };
        }

        pointer operator->()
        {
            return reinterpret_cast<pointer>( &h_ );
        }

        const_pointer operator->() const
        {
            return reinterpret_cast<const_pointer>( &h_ );
        }

    private:
        std::aligned_storage_t<sizeof( value_type ), alignof( value_type )> h_;
    };
}

现在所有构建 block 都已存在,polymorphic_storage<T>可以定义类型。它只是存储一个 std::vector<gut::polymorphic_handle> ,缓冲区和大小信息。

存储类型确保只能添加​​从其模板参数类型派生的类。它只能使用初始实例或一些初始容量(以字节为单位)创建。

template<class D> void ensure_capacity()

这个函数几乎完成了所有的工作。它确保指定为模板参数的类型有足够的容量,并在重新分配时将所有数据传输到新缓冲区。它还更新了 size_成员函数到下一个构造位置。

void emplace_back( D&& value )

这将安置value进入polymorphic_storage<B>并为新放置的值创建一个句柄。

namespace gut
{
    template<class B>
    class polymorphic_storage
    {
    public:
        using byte = unsigned char;
        using size_type = std::size_t;

        ~polymorphic_storage() noexcept
        {
            for ( auto& h : handles_ )
            {
                h->destroy();
            }
            std::free( data_ );
        }

        explicit polymorphic_storage( size_type const initial_capacity )
        {
            byte* new_data
            {
                reinterpret_cast<byte*>( std::malloc( initial_capacity ) )
            };

            if ( new_data )
            {
                data_ = new_data;
                size_ = 0;
                capacity_ = initial_capacity;
            }
            else
            {
                throw std::bad_alloc{};
            }
        }

        template
        <
            class D,
            std::enable_if_t<std::is_base_of<B, std::decay_t<D>>::value, int> = 0
        >
        explicit polymorphic_storage( D&& value )
            : data_{ nullptr }
            , size_{ 0 }
            , capacity_{ 0 }
        {
            using der_t = std::decay_t<D>;
            
            byte* new_data{ reinterpret_cast<byte*>(
                std::malloc( sizeof( der_t ) + alignof( der_t ) ) ) };
            
            if ( new_data )
            {
                data_ = new_data;
                size_ = sizeof( der_t );
                capacity_ = sizeof( der_t ) + alignof( der_t );
                handles_.emplace_back( gut::handle<der_t>
                {
                    ::new ( data_ ) der_t{ std::forward<D>( value ) }
                } );
            }
            else
            {
                throw std::bad_alloc{};
            }
        }

        template
        <
            class D,
            std::enable_if_t<std::is_base_of<B, std::decay_t<D>>::value, int> = 0
        >
        void emplace_back( D&& value )
        {
            using der_t = std::decay_t<D>;

            ensure_capacity<der_t>();
            der_t* p{ ::new ( data_ + size_ ) der_t{ std::forward<D>( value ) } };
            size_ += sizeof( der_t );
            handles_.emplace_back( gut::handle<der_t>{ p } );
        }

        template
        <
            class D,
            std::enable_if_t<std::is_base_of<B, std::decay_t<D>>::value, int> = 0
        >
        void ensure_capacity()
        {
            using der_t = std::decay_t<D>;

            auto padding = gut::calculate_padding<der_t>( data_ + size_ );
            if ( capacity_ - size_ < sizeof( der_t ) + padding )
            {
                auto new_capacity =
                    ( sizeof( der_t ) + alignof( der_t ) + capacity_ ) * 2;

                auto new_data = reinterpret_cast<byte*>(
                    std::malloc( new_capacity ) );

                if ( new_data )
                {
                    size_ = 0;
                    capacity_ = new_capacity;
                    for ( auto& h : handles_ )
                    {
                        h->transfer( new_data + size_, size_ );
                    }
                    std::free( data_ );
                    data_ = new_data;
                }
                else
                {
                    throw std::bad_alloc{};
                }
            }
            else
            {
                size_ += padding;
            }
        }

    public:
        std::vector<gut::polymorphic_handle> handles_;
        byte* data_;
        size_type size_;
        size_type capacity_;
    };
}

这是一个正在使用的存储示例。请注意,类型 der0 , der1der2继承自 base并且有不同的大小和对齐方式。

演示:http://coliru.stacked-crooked.com/a/c304d2b6a475d70c

#include <iostream>
#include <string>

struct base
{
    virtual ~base() = default;
    virtual void print() const = 0;
};

struct der0 : public base
{
    der0( int&& i ) noexcept : i_{ i } {}
    void print() const override { std::cout << "der0_" << i_ << '\n'; }
    int i_;
};

struct der1 : public base
{
    der1( std::string const& s ) noexcept : s_{ s } {}
    void print() const override { std::cout << "der1_" << s_ << '\n'; }
    std::string s_;
};

struct der2 : public base
{
    der2( std::string&& s ) noexcept : s_{ std::move( s ) } {}
    void print() const override { std::cout << "der2_" << s_ << '\n'; }
    std::string s_;
    double d[ 22 ];
};

int main()
{
    gut::polymorphic_storage<base> ps{ 32 };
    ps.emplace_back( der1{ "aa" } );
    ps.emplace_back( der2{ "bb" } );
    ps.emplace_back( der1{ "cc" } );
    ps.emplace_back( der2{ "ee" } );
    ps.emplace_back( der0{ 13 } );
    ps.emplace_back( der2{ "ff" } );

    for ( auto handle : ps.handles_ )
        reinterpret_cast<base*>( handle->src() )->print();
}

关于c++ - 如何在连续内存中多态地存储和访问来自同一继承层次结构的不同类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39376380/

相关文章:

c++ - 我收到 'Invalid Type Int[Int]...' ,但它没有任何意义

c++ - 如何从文件重建 BST

c++ - 以二维项数组作为数据成员并使用运算符+重载的多项式类

java - 在界面中使用@RequestMapping 是不是一个坏主意?

c++ - 类继承层次结构设计问题

c++ - BFS(广度优先搜索)邻接矩阵C++

c++ - 如何在一台 Ubuntu 机器上安装 2 个 Opencv 版本以及如何一次激活一个版本进行编译?

c++ - 静态库中的全局变量有多长时间?

c++ - AIX 中的编程内存监视

haskell - 具有动态请求/响应类型的管道?