类型擦除工作或失败取决于优化级别

时间:2015-02-23 19:30:37

标签: c++ c++11 undefined-behavior type-erasure

我试图通过类型擦除(作为简单格式化文本输出库的一部分)将值类型包装在包装器中。下面的函数print应该包含一个包装在类型擦除包装器结构中的参数,该结构知道(通过函数指针)如何将其转换为字符串并打印它。

当我用以下代码编译它时,它会打印0(或者有时是垃圾)

g++ -std=c++11 -Wall -Wextra -O0 -o test test.cpp

但是在使用-O1-O3编译时它按预期工作(-Og也失败)。使用clang ++,它的行为相反(启用优化时失败)。我也试过g ++ -m32(我的x86_64 Linux Mint盒子上有multilib gcc)和32位和64位mingw-w64交叉编译器。行为类似。此外,clang ++ - libc ++似乎总是失败。

我必须触发一些未定义的行为(因为我发现g ++和clang ++不太可能有相同的bug)。发生了什么,我错过了什么?

#include <iostream>
#include <string>

using namespace std;

// Struct holding a pointer to a type-erased value and a function pointer to
// convert it to a string
struct TypeErasingWrapper {
    void* item;                 // Pointer to type erased value
    string (*to_string)(void*); // Function pointer to convert it to a string
};

// Convert any value pointer to a string (using the proper overload of
// std::to_string
template <typename T>
string toString (void* item)
{
    return to_string(*reinterpret_cast<T*>(item));
}

// Wrap any value in a type-erasing wrapper
template <typename T>
TypeErasingWrapper wrap(T value) {
    return {&value, toString<T>};
}

// Print a type erased value
void print(TypeErasingWrapper wrapper)
{
    cout << wrapper.to_string(wrapper.item) << endl;
}

int main() 
{
    print(wrap(1234));
}

这是没有模板的版本,行为方式相同。

#include <iostream>
#include <string>

using namespace std;

// Struct holding a pointer to a type-erased int and a function pointer to
// convert it to a string
struct TypeErasingWrapper {
    void* item;                 // Pointer to type erased int
    string (*to_string)(void*); // Function pointer to convert it to a string
};

// Convert type-erased int to a string
string toString (void* item)
{
    return to_string(*reinterpret_cast<int*>(item));
}

// Wrap an int in a type-erasing wrapper
TypeErasingWrapper wrap(int value) {
    return {&value, toString};
}

// Print a type erased value
void print(TypeErasingWrapper wrapper)
{
    cout << wrapper.to_string(wrapper.item) << endl;
}

int main() 
{
    print(wrap(1234));
}

1 个答案:

答案 0 :(得分:3)

template <typename T>
TypeErasingWrapper wrap(T value) {
  return {&value, toString<T>};
}

您按value按值。然后,将指针传递给返回值。

value仅持续到函数体结束,此时指针变为悬空指针。

更改TypeErasingWrapper以存储void const*。更改wrap以获取const&Ttemplate<class T> std::string toString( void const * )也是如此。修复剩余的构建错误。在你的时候将reinterpret_cast更改为static_cast

通常,类型擦除代码也会删除所有权(销毁,移动,有时复制)以处理生命周期问题。如果您不这样做,我建议您调用类型blah_view,以便向最终用户说明它不是真正的值类型。

作为最终评论,请在文件范围内停止using namespace std;。简洁很少值得。