我们可以在虚空*上放置小物体吗?

时间:2014-10-31 02:49:35

标签: c++ pointers optimization

假设有以下API:

typedef void callback_t(void* data);

void addCallback(handle_t h, callback_t callback, void* data);

我想将此API包装到更高阶的C ++接口:

template<class F, bool emplace = IsEmplaceable<F>::value>
struct MakeCallback;

class Handle
{
    template<class F>
    void addCallback(F f)
    {
        ::addCallback(_h, MakeCallback<F>::f, MakeCallback<F>::create(f));
    }

    handle_t _h;
};

以便用户可以传递任何可调用对象(例如lambda函数)。

我想应用小对象优化来避免动态分配(例如,对于空的lambdas),特征IsEmplaceable<F>决定F是否可以放在void*中。

对于不可复制的FMakeCallback可以如下实现:

template<class F>
struct MakeCallback<F, false>
{
    static void f(void* data)
    {
        auto f = static_cast<F*>(data);
        (*f)(status);
        delete f;
    }

    static void* create(F& f)
    {
        return new F(std::move(f));
    }
};

对于可以放行的F,我该如何正确实施以下内容?

template<class F>
struct MakeCallback<F, true>
{
    static void f(void* data)
    {
        // from void* to F
    }

    static void* create(F& f)
    {
        // form F to void*
    }
};

更基本的是,如果我们不将它用作指针,void*可以保留非地址值吗?它会是UB吗?

2 个答案:

答案 0 :(得分:0)

在尝试本答案中显示的代码之前,应该提到一个非常大的警告。这样做很可能是非常未定义的,也不是可移植的行为。我强烈建议不要这样做,因为它可能会很长时间地打破未来,你将很难找到原因。

话虽如此,它似乎至少可以用于我的编译器。其他编译器的结果可能会有所不同。我使用union来转换类实例和void *,不知道有任何其他干净的方法来做到这一点。这应该与sizeof( Class ) <= sizeof( void * )一样长,但我不保证不同编译器的行为,甚至我在完全相同的设置上使用完全相同的编译器。

#include <iostream>

using namespace std;

class Small {
public:
    int i;
};

void *castToVoid( Small& s ) {
    union {
        Small s;
        void *p;
    } un;
    un.s = s;
    return un.p;
}

Small castToSmall( void *p ) {
    union {
        Small s;
        void *p;
    } un;
    un.p = p;
    return un.s;
}

int main( ) {
    Small s;
    s.i = 100;

    void *p = castToVoid( s );

    s.i = 200;
    cout << p << endl; // Prints 0x64

    Small s2 = castToSmall( p );
    cout << s2.i << endl; // Prints 100
}

或此示例用于转换为/来自void *

void *castToVoid( Small& s ) {
    void **p = reinterpret_cast< void ** >( &s );
    return *p;
}

Small castToSmall( void *p ) {
    Small *s = reinterpret_cast< Small * >( &p );
    return *s;
}

答案 1 :(得分:0)

根据C ++标准(草案N3797),它是实现定义的行为。

§3.7.4.2/p4

  

通过无效指针值间接传递并传递无效   指向释放函数的指针值具有未定义的行为。的不限   其他使用无效指针值具有实现定义   行为

脚注38:

  

某些实现可能会定义复制无效指针   value导致系统生成的运行时错误

§3.7.4.3/p4

  

实现可能放松了指针安全性,在这种情况下   指针值的有效性不取决于它是否为a   安全派生的指针值。或者,实现可以   有严格的指针安全性,在这种情况下指针值指的是   动态存储持续时间不是安全派生的对象   除非引用,否则指针值是无效的指针值   先前已声明完整对象已被声明可达(20.7.4)[...] 这是实现   定义实现是否已放宽或严格指针安全性

(强调我的)

因此,如果实现放宽指针安全性是安全的,我们可以使用answer @Smith_61中显示的union技巧来避免严格的别名。