是否应允许使用std::unique_ptr<void>?

28

这是一个非常简单的问题。考虑以下代码:

#include <iostream>
#include <memory>

typedef std::unique_ptr<void> UniqueVoidPtr;

int main() {
    UniqueVoidPtr p(new int);
    return 0;
}

在cygwin(g++ 4.5.3)下使用以下命令进行编译g++ -std=c++0x -o prog file.cpp完全没有问题。然而,在微软编译器(无论是VS 2010还是2013)下编译时,我会得到以下错误:

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\INCLUDE\memory(2067) : error C2070: 'void': illegal sizeof operand
        C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\INCLUDE\memory(2066) : while compiling class template member function 'void std::default_delete<_Ty>::operator ()(_Ty *) const'
        with
        [
            _Ty=void
        ]
        C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\INCLUDE\type_traits(650) : see reference to class template instantiation 'std::default_delete<_Ty>' being compiled
        with
        [
            _Ty=void
        ]
        C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\INCLUDE\memory(2193) : see reference to class template instantiation 'std::tr1::is_empty<_Ty>' being compiled
        with
        [
            _Ty=std::default_delete<void>
        ]
        foo1.cpp(7) : see reference to class template instantiation 'std::unique_ptr<_Ty>' being compiled
        with
        [
            _Ty=void
        ]

这是正常的吗?我正在编写一个类,希望在类中使用唯一指针。在尝试为该类编写移动构造函数语义时,我遇到了问题(我猜测这是因为我最终正确编码了我的移动构造函数:即其他错误已经被修复)。


9
std::unique_ptr中,默认的删除器会在指针上调用delete,如果你在void*上调用delete,你期望会发生什么? - David Rodríguez - dribeas
2
@DavidRodríguez-dribeas:实际上,default_delete 不应该在 void* 上调用 delete(请参见 ixSci 的答案)。此外,如果它确实在 void* 上调用了 delete,那么这将是未定义行为(请参见 Nevin 的答案),在这种情况下,可以期望任何结果(不一定是错误)。 - Cassio Neri
1
为什么shared_ptr<void>是合法的,而unique_ptr<void>是不合法的? - Ciro Santilli OurBigBook.com
4个回答

24

MSVC是正确的,而GCC是错误的:

标准(3.9/5):

未完全定义的对象类型和void类型是不完整的类型

标准(20.7.1.1.2/4):

如果T是不完整的类型,则程序是非法的


22
unique_ptr 的一个意图设计是将这种行为定义为非法的,而不是未定义的行为。使用已弃用的 auto_ptr 进行相同的操作是未定义的行为。两者最大的区别是,非法行为需要诊断信息,而未定义行为可以静默地执行任何操作。总之,如果你的 custom_deleter 能够处理 void* 的处理,那么 unique_ptr<void, custom_deleter> 是完全有效的。 - Howard Hinnant

23

GCC实际上有防止这种情况发生的代码,但直到最近才开始起作用。

GCC的unique_ptrdefault_deleter::operator()中有一个静态断言,应该拒绝不完整的类型:

    static_assert(sizeof(_Tp)>0,
                  "can't delete pointer to incomplete type");

然而,作为扩展,GCC支持sizeof(void),因此断言不会失败,并且因为它出现在系统头文件中,甚至不会发出警告(除非您使用-Wsystem-headers)。
最近我自己发现了这个问题,所以为了解决它,我添加了这个10天前
    static_assert(!is_void<_Tp>::value,
                  "can't delete pointer to incomplete type");

因此,在主干上使用最新的代码,您的示例无法编译,这符合标准要求。


8
问题归结为:
void* p = new int;
delete p;

看n3797 5.3.5 Delete,我认为delete p是未定义的行为,因为类型不匹配,所以编译器的任一行为都是可以接受的,因为代码存在缺陷。

注意:这与shared_ptr<void>不同,因为它使用类型擦除来跟踪传入指针的原始类型。


shared_ptr有何不同?它使用类型擦除来删除,而不是指针类型。 - ixSci
2
@ixSci 看一下实现?删除器是基于原始指针类型进行模板化的。 - gx_
3
你说的代码片段确实会产生未定义行为。然而,如果所指类型不完整,default_delete 应该不调用 delete,否则程序将不合法(请参见 ixSci 的回答)。 - Cassio Neri

3
不要删除 void * 类型的变量。
如果您想使用类似 Win32 Handles 的东西,请提供自定义的删除器。
例如:
void HandleDeleter(HANDLE h)
{
    if (h) CloseHandle(h);
}

using UniHandle = unique_ptr<void, function<void(HANDLE)>>;

然后:

UniHandle ptr(..., HandleDeleter);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接