unique_ptr 删除技巧:哪个编译器是正确的?

unique_ptr deleter trick: which compiler is correct?

我正在研究 this particular SO 如何为自定义删除器指针节省内存 space。在答案的底部,它提供了C++11中的自定义编写版本。

在尝试理解代码几十分钟后,我发现 Big 3 之间存在一些编译器不一致的地方,其中 clang 编译,而其他两个则抱怨编译器错误。 (Live Demo)


我的代码与其他 SO 答案略有不同。开始了:

#include <cstdlib>
#include <iostream>
#include <memory>
#include <type_traits>

using namespace std;

template <class T, T t>
struct val {
    constexpr operator typename decay<T>::type() const noexcept {
        return t;
    }
};

using default_free = val<decltype(free), free>;

int main(void) {
    unique_ptr<void, default_free> p(malloc(42));
    cout << p.get() << endl;
    p.reset();
    cout << p.get() << endl;

    return 0;
}

如果我这里说错了请指正。在我看来,诀窍是提供一个 constexpr 函数,该函数可以将 default_delete 类型的对象转换为值为 t 的函数指针(free 作为其实例)。

所以问题是:哪个编译器是正确的?

这似乎是一个 gcc 错误,一个完全不同的 MSVC 错误。

gcc 不能这样做:

template <typename T, T t> struct foo {};

使用函数类型实例化时。 T 必须是触发此错误的模板参数; template <void t(void*) noexcept> 有效。

template <typename T, T* t> struct foo {};

解决了问题。

MSVC 很好地吸收了前面的任何一种构造,但它不能将 free 用作非类型模板参数。 (其他标准 C 库函数也是如此。)一个简单的解决方案是使用限定名称(::freestd::free,两者都有效)。在另一个函数中包装 free 也解决了这个问题。这是因为 MSVC 显然认为(在这种情况下)::freestd::free 是不同的函数,并且无法消除歧义。可以在没有标准库的情况下重现这一点:

void foo(void*);

namespace moo {
  using ::foo;
}

using namespace moo;

现在 plain foo 不能用作模板参数。