指向对象开头的指针 (C++)

Posted

技术标签:

【中文标题】指向对象开头的指针 (C++)【英文标题】:Pointer to the start of an object (C++) 【发布时间】:2010-08-15 15:39:44 【问题描述】:

我需要一种方法来获取指向 C++ 中对象开头的指针。此对象在模板中使用,因此它可以是任何类型(多态或非多态),并且可能是使用多重继承的对象。

我发现this article 描述了一种在 T 是多态类型的情况下使用 typeid 和 dynamic_cast 到 void* 的方法(参见“动态转换”部分)。

这在 MSVC 上运行得非常好,但是在 GCC (4.x) 上,当它与非多态类型一起使用时,它似乎会失败并吐出编译器错误。

有谁知道以下方法:

让 GCC 自行运行,并正确评估 typeid 或另一种方法,将在 GCC 上编译

以下是我目前用来尝试实现此目的的代码。

template <typename T>
void* dynamicCastToVoidPtr(T *const ptr)

    // This is done using a separate function to avoid a compiler error on some 
    // compilers about non-polymorphic types when calling startOfObject
    return dynamic_cast<void*>(ptr);


template <typename T>
void* startOfObject(T *const ptr)

    // In cases of multiple inheritance, a pointer may point to an offset within 
    // another object
    // This code uses a dynamic_cast to a void* to ensure that the pointer value 
    // is the start of an object and not some offset within an object
    void *start = static_cast<void*>(ptr);
    if(start)
        typeid(start = dynamicCastToVoidPtr(ptr), *ptr);
    return start;


template <typename T>
void doSomethingWithInstance(T *const instance)

    // Here is where I need to get a void* to the start of the object
    // You can think of this as the deleteInstance function of my memory pool
    // where the void* passed into freeMemory should point to the
    // start of the memory that the memory pool returned previously
    void *start = startOfObject(instance);
    if(start)
        allocator->freeMemory(start);

谢谢。

【问题讨论】:

$10/5 states- "[注意:基类子对象的布局 (3.7) 可能与相同类型的最派生对象的布局不同。基类子对象可能具有多态行为 (12.7) 不同于同一类型的最派生对象的多态行为。基类子对象的大小可能为零(第 9 条);但是,具有相同类类型且属于同一类的两个子对象最派生对象不得分配在同一地址 (5.10)。]" struct point int x; int y; ; point *p = new point(); doSomethingWithInstance(&amp;p-&gt;x); 会发生什么? 洛根;那将是对该功能的无效使用。它只能用于已在堆上分配的实例。 为什么要这样做?我只是感兴趣——对我来说这听起来很邪恶。 @MrD 所以如果我必须知道如何正确使用该函数,那么从 go 一词中传入对象的开头是否如此不合理?请记住,如果我想破坏对象,无论如何我都需要它的“开始”,在对象的“中间”调用析构函数是个坏主意。我认为这里的适当方法是使用分配器的元数据来查找 allocation 的开始,给定指向分配中间的指针,如果你想完全支持这种用法. 【参考方案1】:

gcc 的确切错误信息是

错误:无法将 &amp;nstruct N* 类型)动态转换为 void* 类型(源类型不是多态的)

这可以通过将boost::is_polymorphicboost::enable_ifboost::disable_if 结合使用来处理,不幸的是,gcc 被明显的方法阻塞了,所以这里是解决方法:

template <class T>
void* address_of_impl(T* p, boost::enable_if< boost::is_polymorphic<T>, int >)

  return dynamic_cast<void*>(p);


template <class T>
void* address_of_impl(T* p, ...)  return static_cast<void*>(p); 

template <class T>
void* address_of(T* p)  return address_of_impl(p, 0); 

我们使用 SFINAE 是我们的优势(省略号总是在重载决议中被认为是最后一个,因此编译器首先尝试使用 dynamic_cast 版本,由于 enable_if 而对于非多态类型失败)。

我已经在 gcc 3.4 上对其进行了测试,它通过了。我正在another question 调查为什么使用disable_if 而不是... 不起作用。

编辑

这是一个简单的错字(忘记了::type 位):

template <class T>
typename boost::enable_if< boost::is_polymorphic<T>, void* >::type
address_of(T* p)  return dynamic_cast<void*>(p); 

template <class T>
typename boost::disable_if< boost::is_polymorphic<T>, void* >::type
address_of(T* p)  return static_cast<void*>(p); 

【讨论】:

【参考方案2】:

我做了一些调查,您可能在这里发现了 GCC 中的错误;我会report it。但是,可能有一些我找不到的规则说,在这种情况下,逗号运算符左侧的模板函数确实需要实例化,即使没有评估整个表达式;在那种情况下,文章中的技术就是错误的。

我建议你看看是否可以让boost::type_traits::is_polymorphic 为你工作。

【讨论】:

感谢您的建议,但是此代码是库的一部分,我试图避免对 Boost 的必需依赖。 你可以阅读 boost/type_traits/is_polymorphic.hpp 并弄清楚它在做什么然后自己做:) (祝你好运,不过,Boost 有很多东西,但可以理解的不是其中之一他们。) 是的,我可能会看看那个。如果我能在编译时判断一个类型是否是多态的,那么我可以使用模板特化来解决这个问题。 boostpro 上有一个提取工具,可以清理 boost 头文件(删除编译器不需要的任何东西)。【参考方案3】:

我从another question 找到了一个解决方案,如果一个类型是多态的,我可以在编译时进行计算,然后我可以将它与模板特化一起使用以使用正确的强制类型转换。显然,如果编译器在子对象之间添加填充,则此方法可能会中断,但我希望可以在某些已知情况下添加一些编译时断言来捕获它。它确实可以在 MSVC 和 GCC 上正确编译和运行。

这是确定类型是否为多态的代码。

#define SIMPLE_POLYMORPHIC(TYPE, POLYMORPHIC)   \
    template <>                                 \
    struct IsPolymorphic<TYPE>                  \
                                               \
        static const bool value = POLYMORPHIC;  \
    ;

template <typename T>
struct IsPolymorphic

    struct Derived : public T  virtual ~Derived(); ;
    static const bool value = (sizeof(Derived) == sizeof(T));
;

SIMPLE_POLYMORPHIC(int, false);
SIMPLE_POLYMORPHIC(unsigned int, false);
// ... do this for all intrinsic or non-derivable types

根据类型是否为多态来执行转换的代码。

template <typename T, bool isPolymorphic = IsPolymorphic<T>::value>
struct StartOfObject

    static void* getStart(T *const ptr)
    
        return static_cast<void*>(ptr);
    
;

template <typename T>
struct StartOfObject<T, true>

    static void* getStart(T *const ptr)
    
        if(ptr)
            return dynamic_cast<void*>(ptr);
        return NULL;
    
;

还有一个测试用例。

#define CLASS_STUFF(CLASS)      \
    public:                     \
        CLASS()               \
        virtual ~CLASS()      \
        int m_##CLASS;

class A

    CLASS_STUFF(A);
;

class B : public A

    CLASS_STUFF(B);
;

class C

;

#include <iostream>

int main()

    std::cout << IsPolymorphic<A>::value << std::endl;
    std::cout << IsPolymorphic<B>::value << std::endl;
    std::cout << IsPolymorphic<C>::value << std::endl;
    std::cout << IsPolymorphic<int>::value << std::endl;

    StartOfObject<A>::getStart(new A());
    StartOfObject<B>::getStart(new B());
    StartOfObject<C>::getStart(new C());
    StartOfObject<int>::getStart(new int());

    return 0;
;

【讨论】:

以上是关于指向对象开头的指针 (C++)的主要内容,如果未能解决你的问题,请参考以下文章

C++指向对象成员的指针

在 C++ 中使用指针和指向指针的指针读取和存储序列化对象的快速方法

C++对象指针-指向对象的指针

C++对象指针-指向对象的指针

C++指向常对象的指针变量

C++指向常对象的指针变量