【问题标题】:Providing an (empty) user-defined destructor causes compilation error提供(空)用户定义的析构函数会导致编译错误
【发布时间】:2013-04-18 01:20:19
【问题描述】:

当我没有用户定义的析构函数时编译得非常好(在 GCC 4.7.2 上)的代码,即使提供了 empty 用户定义的析构函数也会产生错误:

#include <memory>

class Test
{
   std::unique_ptr<int> val;
};

template <typename Type>
class B
{
public:
   //destructor:
   // if I comment this out, the code compiles just fine:
   ~B() { }

private:
   Test a;
};

int main()
{
   auto s = B<int>();
}

析构函数没有被注释掉时产生的错误的要点是:

  • Test 的复制构造函数不存在,并且没有隐式创建,因为它的格式不正确
  • 有人试图将已删除的复制构造函数用于 unique_ptr。

对于任何感兴趣的人来说,完整的错误输出在这篇文章的底部。

我知道unique_ptr 不能被复制构造(除非参数是右值),因此编译器不可能为类Test 生成有效的隐式复制构造函数。

我不明白为什么定义一个 empty 析构函数会突然需要这些复制工具。显然,当使用unique_ptrs 之类的东西时,这是不可能提供的。

这里有人能告诉我为什么会这样吗?

析构函数没有被注释掉时的完整错误输出:

In file included from /usr/include/c++/4.7/list:64:0,
                 from ../../Dropbox/Programming/C++/test/main.cpp:2:
/usr/include/c++/4.7/bits/stl_list.h: In instantiation of 'std::_List_node<_Tp>::_List_node(_Args&& ...) [with _Args = {const Test&}; _Tp = Test]':
/usr/include/c++/4.7/ext/new_allocator.h:110:4:   required from 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::_List_node<Test>; _Args = {const Test&}; _Tp = std::_List_node<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:503:8:   required from 'std::list<_Tp, _Alloc>::_Node* std::list<_Tp, _Alloc>::_M_create_node(_Args&& ...) [with _Args = {const Test&}; _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc>::_Node = std::_List_node<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:1533:63:   required from 'void std::list<_Tp, _Alloc>::_M_insert(std::list<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const Test&}; _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc>::iterator = std::_List_iterator<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:997:9:   required from 'void std::list<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc>::value_type = Test]'
/usr/include/c++/4.7/bits/stl_list.h:1466:6:   required from 'void std::list<_Tp, _Alloc>::_M_initialize_dispatch(_InputIterator, _InputIterator, std::__false_type) [with _InputIterator = std::_List_const_iterator<Test>; _Tp = Test; _Alloc = std::allocator<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:582:9:   required from 'std::list<_Tp, _Alloc>::list(const std::list<_Tp, _Alloc>&) [with _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc> = std::list<Test>]'
../../Dropbox/Programming/C++/test/main.cpp:11:7:   required from here
/usr/include/c++/4.7/bits/stl_list.h:115:71: error: use of deleted function 'Test::Test(const Test&)'
../../Dropbox/Programming/C++/test/main.cpp:5:7: note: 'Test::Test(const Test&)' is implicitly deleted because the default definition would be ill-formed:
../../Dropbox/Programming/C++/test/main.cpp:5:7: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>; std::unique_ptr<_Tp, _Dp> = std::unique_ptr<int>]'
In file included from /usr/include/c++/4.7/memory:86:0,
                 from ../../Dropbox/Programming/C++/test/main.cpp:3:
/usr/include/c++/4.7/bits/unique_ptr.h:262:7: error: declared here

【问题讨论】:

  • 啊,很高兴知道;我的印象是,是否创建了隐式移动构造函数/赋值运算符仍然取决于编译器或其他东西。我没有意识到这是标准的一部分。

标签: c++ c++11 compiler-errors destructor unique-ptr


【解决方案1】:

如果定义~B(),这会抑制B 的移动构造函数,因此编译器会尝试生成复制构造函数但失败,因为unique_ptr 不可复制构造。

如果省略~B(),则会生成B的移动构造函数,并在main中使用。

可以请求自动生成的move构造函数:

B(B &&) = default;

这是标准中的一项功能,用于向后兼容 C++03 代码;根据三法则,编写自己的析构函数(等)的代码被假定为管理自己的资源,因此除非明确要求,否则自动生成移动构造函数是不合适的。

【讨论】:

  • 那么我的想法是对的吗,auto s = B&lt;int&gt;(); 行首先构造了一个临时 B,然后调用 B 的隐式移动赋值运算符(在 s 中),进而调用 B 的隐式移动构造函数?跨度>
  • @lytnus auto s = B&lt;int&gt;() 是一个初始化,不是赋值,所以调用构造函数(虽然构造函数调用可以省略,但它必须可用)。
  • 啊好吧,这对我来说很有意义!谢谢:)
  • 请注意,如果您不落入新的 auto 无处不在 趋势中,只需键入 B&lt;int&gt; s; 就不需要定义移动或复制构造函数。我觉得很有趣 auto 是如何被创建来减少打字并突然被用来打字的......
  • @DavidRodríguez-dribeas:+1 告诉我
【解决方案2】:

嗯,你自己说的;当您提供用户定义的析构函数时,您会抑制编译器生成诸如隐式移动构造函数之类的东西的能力。您的用户定义的析构函数的内容(无论是空的还是其他的)是完全不相关的。

[C++11: 12.7/9]: 如果类X的定义没有显式声明移动构造函数,当且仅当

  • X 没有用户声明的复制构造函数,
  • X 没有用户声明的复制赋值运算符,
  • X 没有用户声明的移动赋值运算符,
  • X 没有用户声明的析构函数,并且
  • 移动构造函数不会被隐式定义为已删除。

[ 注意: 当移动构造函数没有被隐式声明或显式提供时,原本会调用移动构造函数的表达式可能会调用复制构造函数。 —结束注释]

注释中的文字指出了您所看到的确切情况,导致编译失败,因为unique_ptr无法被复制;虽然没有复制它,虽然s的初始化可能不需要复制/移动,但该操作仍然需要可用(根据[C++11: 12.8/31-32])。

【讨论】:

  • 谢谢,很高兴知道移动构造函数被隐式定义为标准的一部分;我的印象是编译器是否选择为你做这件事是命中注定的(尽管我确信无论如何我仍然会感到困惑!)
  • @lytnus 您可能已经获得了这种印象,因为 MSVC 缺少隐式生成。
  • 是的;坦率地说,如今 MSVC 是否会选择为您做任何事情,这很容易受到影响。
猜你喜欢
  • 1970-01-01
  • 2010-10-17
  • 2019-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-11
  • 2015-07-02
  • 2013-04-30
相关资源
最近更新 更多