4

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

  • 从任何指针类型构造,可以编写如下代码

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

    T* ptr = g_ptr.recover(), or
    auto* ptr = g_tr.recover() 
    
  • 更新:根据一些评论,以上无法在 C++ 中完成,然后类似于

    recover<Type>(g_ptr)
    

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

  • g_pointer 可以包含在 std::vector 或普通数组中,这基本上意味着

    sizeof(g_pointer) // a predetermined constant number,
    

    (更新:这总是正确的,前提是可以正确实现这样的类,谢谢指出。)

我刚刚发现 boost::any,看一下它的介绍似乎暗示它可能是我想要的,尽管可能并非如此。因此,欢迎任何熟悉 boost::any 的人发表评论。

更新:(回复一些评论)

  • g_pointer 类型对象应该知道它指向的对象的底层类型。因此,recover 方法应始终返回该类型的指针。
  • 通用指针类型,意味着对任何对象的引用,恕我直言,对于任何支持面向对象范式的语言来说,这是一个合理的要求。

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

4

2 回答 2

5

It is impossible in C++. Because the type of the expression g_ptr.recover() is determined at compile time, it cannot store information of the underlying type, which is determined at runtime.


If you can tolerate expressions like g_ptr.recover<T>(), you can implement g_pointer by wrapping a void* and a const std::type_info& that stores the information of the actual type the pointer points to, e.g.

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;
};

Note this g_pointer behaves like a raw pointer rather than a smart pointer, which means it does not own the object it points to.


There is still a defect in the implementation above: const T* cannot be implicitly converted to void*, thus the general pointer cannot hold const T*. To handle const-qualifiers, you can change the type of _data to const void* and use const_cast when recovering. In addition, recover shall reject to return a pointer to non-const object from a g_pointer holding a pointer to const object. However, typeid operator ignores top const-qualifiers, so we need an additional data member to record whether the pointer points to an originally const object.

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
};
于 2018-02-02T04:47:09.360 回答
1

没有什么能阻止您void*在 C++ 中使用 C 构造,例如 a 。然而,它通常不受欢迎,因为如果代码以非预期的方式使用,或者所述操作的后果没有被完整记录,它可能会为各种错误打开大门。

话虽如此,您实际上是在要求将 a 包装void*在一个类中,然后该类可以在 a 中使用std::vector,然后再访问。

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

generic_ptr.hpp

#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]);

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

try {
    double* d = (double*)items[0];
    // items[0] is an int, so this will throw a std::bad_cast
    std::cout << "d = " << *d << std::endl;
} catch (std::bad_cast& ex) {
    std::cout << ex.what() << std::endl;
}

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

我希望这可以帮助您弄清楚。

于 2018-02-02T08:38:36.973 回答