如何在C ++中实现通用指针类型

时间:2018-02-02 03:11:50

标签: c++ pointers boost-any heterogeneous-array

在C中,可以将数据指针分配给void指针,然后将其转换回原始类型,该数据指针将被恢复。语言标准保证这种转换不会丢失信息。这通常意味着(不一定,但对大多数平台来说都是如此),void指针的大小与数据指针的大小相同。因此,可以指望这些事实使用void指针作为异构类型的一般指针,而void指针本身具有统一的大小和表示。例如,一个有一个void指针数组,其元素指向动态分配的不同类型的对象。构造这样的数组使某些事情变得方便。 我的问题是:如何实现类似的东西,C ++中的通用指针类型,符合以下条件:(假设g_pointer是类名)

  • 从任何指针类型构造,可以编写类似

    的代码
    g_pointer g_ptr =  g_pointer(new T())
    
  • 恢复原始指针

    T* ptr = g_ptr.recover(), or
    auto* ptr = g_tr.recover() 
    
  • 更新:根据一些评论,上述内容无法在C ++中完成,那么<​​/ p>

    recover<Type>(g_ptr)
    

    应该足够了,抛出异常类型不兼容。

  • g_pointer可以包含在std :: vector或plain数组中,基本上就是

    sizeof(g_pointer) // a predetermined constant number,
    

    (更新:这一切都是正确的,如果这样的课程可以正确实施,感谢您指出。)

我刚刚发现了boost :: any,看看它的介绍似乎表明它可能是我想要的,尽管可能不是这样。所以任何熟悉boost :: any的人都欢迎发表评论。

更新:(回复一些评论)

  • g_pointer类型对象应该知道它指向的对象的基础类型。因此,recover方法应该总是返回该类型的指针。
  • 一般指针类型,意味着对任何对象的引用,恕我直言,对任何支持面向对象范式的语言都是合理的。

更新:谢谢@Caleth,std :: any很棒。

2 个答案:

答案 0 :(得分:5)

在C ++中是不可能的。因为表达式g_ptr.recover()的类型是在编译时确定的,所以它不能存储在运行时确定的基础类型的信息。

如果您可以容忍像g_ptr.recover<T>()这样的表达式,则可以通过包装g_pointervoid*来实现const std::type_info&class g_pointer { public: template <class T> constexpr g_pointer(T *data) noexcept : _data(data), _object_type(typeid(T)) {} template <class T> T* recover() const { if (typeid(T) == _object_type) return static_cast<T*>(_data); else throw std::bad_cast{}; } private: void *_data; const std::type_info &_object_type; }; g_pointer存储指针指向的实际类型的信息,例如

const T*

请注意,此void*的行为类似于原始指针而非智能指针,这意味着它不拥有它指向的对象。

上面的实现中仍然存在缺陷:const T*无法隐式转换为_data,因此通用指针无法保存const void*。要处理const限定符,您可以将const_cast的类型更改为recover并在恢复时使用g_pointer。此外,typeid将拒绝从持有指向const对象的指针的class g_pointer { public: template <class T> constexpr g_pointer(T *data) noexcept : _data(data), _object_type(typeid(T)), _is_const(std::is_const_v<T>) // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ change here { } template <class T> T* recover() const { if ( typeid(T) != _object_type || (_is_const && !std::is_const_v<T>) // try to obtain T* while const T* is held ) { throw std::bad_cast{}; } else return static_cast<T*>(const_cast<void*>(_data)); // ^^^^^^^^^^^^^^^^^ change here } private: const void *_data; // ^^^^^ change here const std::type_info &_object_type; bool _is_const; // <-- record whether the pointer points to const T }; 返回指向非const对象的指针。但是,sudo apt-get install XXX 运算符忽略了顶部const限定符,因此我们需要一个额外的数据成员来记录指针是否指向最初的const对象。

<p-panelMenu [model]="items" [style]="{'width':'300px'}" (click)="clicked($event)"></p-panelMenu>`<p-dialog header="Title" [(visible)]="display">
page 1

答案 1 :(得分:1)

没有什么可以阻止你在C ++中使用C构造,比如this.$nextTick(function() { this.$emit('galleryVisible', this.gallery) }); 。然而,它通常不赞成,因为如果代码以无意的方式使用,或者所述操作的后果没有完全记录,它可以为各种错误打开大门。

话虽如此,您基本上要求将void*包装在一个类中,然后可以在void*中使用,然后再访问。

这里是我前一段时间编写的一些框架的代码,以达到类似的效果:

generic_ptr.hpp

std::vector

然后使用它:

的main.cpp

#include <exception>
#include <typeinfo>
#include <map>

namespace so {
    std::map<std::size_t, std::size_t> type_sizes;

    template < typename T >
    std::size_t type_id()
    {
        static char tid;
        std::size_t sz = reinterpret_cast<std::size_t>(&tid);
        so::type_sizes[sz] = sizeof(T);
        return sz;
    }

    template < typename T >
    inline std::size_t type_id(const T& t)
    {
        return so::type_id<T>();
    }

    template < typename T >
    inline std::size_t type_id(const T *const t)
    {
        return so::type_id<T>();
    }

    template < typename T, typename C >
    inline bool type_of()
    {
        return so::type_id<T>() == so::type_id<C>();
    }

    template < typename T, typename C >
    inline bool type_of(const C& c)
    {
        return so::type_of<T, C>();
    }

    template < typename T, typename C >
    inline bool type_of(const C *const c)
    {
        return so::type_of<T, C>();
    }

    template < typename T, typename C >
    inline bool type_of(const T& t, const C& c)
    {
        return so::type_of<T, C>();
    }

    template < typename T, typename C >
    inline bool type_of(const T *const t, const C *const c)
    {
        return so::type_of<T, C>();
    }

    class generic_ptr
    {
        public:
            generic_ptr() : m_ptr(0), m_id(0) { }

            template < typename T >
            generic_ptr(T *const obj) : 
                m_ptr(obj), m_id(so::type_id<T>())
            {
            }

            generic_ptr(const generic_ptr &o) : 
                m_ptr(o.m_ptr), m_id(o.m_id)
            {
            }

            ~generic_ptr()
            {
                this->invalidate();
            }

            static generic_ptr null()
            {
                return generic_ptr();
            }

            void invalidate()
            {
                this->m_ptr = 0;
                this->m_id = 0;
            }

            template < typename T >
            bool is_type() const
            {
                return this->m_id == so::type_id<T>();
            }

            template < typename T >
            void gc()
            {
                delete ((T*)this->m_ptr);
                this->invalidate();
            }

            bool valid() const
            {
                return (this->m_ptr != 0);
            }

            operator bool() const
            {
                return (this->m_ptr != 0);
            }

            bool operator!() const
            {
                return (!operator bool());
            }

            generic_ptr& operator=(const generic_ptr &o)
            {
                this->m_ptr = o.m_ptr;
                this->m_id = o.m_id;
                return *this;
            }

            template < typename T >
            const generic_ptr& operator=(T *const obj)
            {
                this->m_ptr = obj;
                this->m_id = so::type_id<T>();
                return *this;
            }

            template < typename T >
            operator T *const() const
            {
                if (this->m_id != so::type_id<T>()) {
                    throw std::bad_cast();
                }
                return static_cast<T *const>(
                    const_cast<void *const>(this->m_ptr)
                );
            }

            template < typename T >
            operator const T *const() const
            {
                if ((this->m_id != so::type_id<T>()) && (this->m_id != so::type_id<const T>())) {
                    throw std::bad_cast();
                }
                return static_cast<const T *const>(this->m_ptr);
            }

            operator void *const() const
            {
                return const_cast<void*>(this->m_ptr);
            }

            operator const void *const() const
            {
                return this->m_ptr;
            }

            bool operator==(const generic_ptr& o) const
            {
                return (this->m_ptr == o.m_ptr && this->m_id == o.m_id);
            }

            bool operator!=(const generic_ptr& o) const
            {
                return !(*this == o);
            }

            std::size_t hash() const
            {
                return this->m_id;
            }

        private:
            const void* m_ptr;
            std::size_t m_id;
    };
}

当然,你仍然需要知道类型并跟踪内存,但你可以修改代码来处理它;使用运算符重载,您不需要以这种方式使用#include <iostream> #include <vector> #include "generic_ptr.hpp" class MyClass { public: MyClass() : m_val1(10), m_val2(20), m_val3(10), m_val4(2) {} MyClass(int a, int b, int c, int d) : m_val1(a), m_val2(b), m_val3(c), m_val4(d) {} friend std::ostream& operator<<(std::ostream& os, const MyClass& mc) { os << mc.m_val1 << " + " << mc.m_val2 << " + " << mc.m_val3 << " + " << mc.m_val4 << " = " << (mc.m_val1 + mc.m_val2 + mc.m_val3 + mc.m_val4); return os; } private: int m_val1; int m_val2; int m_val3; int m_val4; }; template < typename T > void print(so::generic_ptr& g_ptr) { std::cout << "sizeof = " << so::type_sizes[g_ptr.hash()] << ", val = " << *((T*)g_ptr) << std::endl; } template < typename T > void cleanup(so::generic_ptr& g_ptr) { delete ((T*)g_ptr); } int main(int argc, char* argv[]) { std::vector<so::generic_ptr> items; items.push_back(new int(10)); items.push_back(new double(3.14159)); items.push_back(new MyClass()); items.push_back(new char(65)); items.push_back(new MyClass(42,-42,65536,9999)); items.push_back(new int(999)); for (auto i : items) { if (i.is_type<int>()) { print<int>(i); } else if (i.is_type<char>()) { print<char>(i); } else if (i.is_type<double>()) { print<double>(i); } else if (i.is_type<MyClass>()) { print<MyClass>(i); } } int* i = (int*)items[0]; std::cout << "i = " << *i << std::endl; *i = 500; std::cout << "i = " << *i << std::endl; try { double* d = (double*)items[0]; std::cout << "d = " << *d << std::endl; } catch (std::bad_cast& ex) { std::cout << ex.what() << std::endl; } for (auto i : items) { if (i.is_type<int>()) { print<int>(i); cleanup<int>(i); } else if (i.is_type<char>()) { print<char>(i); cleanup<char>(i); } else if (i.is_type<double>()) { print<double>(i); cleanup<double>(i); } else if (i.is_type<MyClass>()) { print<MyClass>(i); cleanup<MyClass>(i); } } return 0; } 函数,您可以像recover代码print一样进行强制转换,并且可以访问它通过原始指针,就像上一个*((T*)g_ptr)语句之前:

for..each

如果您尝试在无效类型之间进行转换,则此类还内置了无效的类型转换:

int* i = (int*)items[0];
*i = 500;
print<int>(items[0]);

说实话,在这种情况下,便利性可能胜过安全性,因此如果您需要一组不一致或无法使用基类定义的类型,您可能需要重新考虑您的问题。试图以C ++方式实现。

我希望这可以帮助你获得一些清晰度。