【问题标题】:Forward declaration with smart pointers and inherited constructor带有智能指针和继承构造函数的前向声明
【发布时间】:2016-12-11 13:08:19
【问题描述】:

根据question,如果所有构造函数和析构函数都不是内联的(则需要完全定义的类型),则可以转发声明智能指针。当没有提供析构函数时,编译器将声明一个并提供一个内联定义,然后要求在头文件中完全知道智能指针中的类型。这同样适用于默认构造函数。

但是,我发现它也适用于继承的构造函数,这让我有点困惑。考虑:

class Base
{
public:
    Base(); //defined in cpp
};

class SomeClass;

class Derived : public Base
{
    using Base::Base;
    ~Derived(); //defined in cpp

    std::unique_ptr<SomeClass> ptr;
};

除非Derived 构造函数被显式声明并且只在源文件中定义,否则它不会编译。为什么? Base 构造函数不是内联的,据我所知 using 指令应该以与其他成员类似的方式导致构造函数的“继承”。还是编译器将其解释为“为我声明与Base 中相同的构造函数并内联定义它们”?

【问题讨论】:

  • 为什么不使用Rule of zero
  • @πάνταῥεῖ 因为编译器定义的构造函数/析构函数是隐式内联的,需要在头文件中完全知道传递给智能指针的类型(前向声明是不够的)。
  • 非常好的问题,确实。谢谢!

标签: c++ c++11 constructor smart-pointers forward-declaration


【解决方案1】:

最后一句话就是你的答案。编译器将using Base::Base; 解释为

“我希望Derived 具有与Base 具有相同签名集的构造函数。请为我定义它们。”

【讨论】:

  • 然后它遵循与其他隐式声明的构造函数/析构函数相同的规则,默认情况下内联定义。我想没有简单的方法解决它(继承构造函数,但让编译器在其他地方定义它们,而不是将它们内联在标题中)。
【解决方案2】:

首先让我们用最少的代码重现问题:

#include <memory>

class SomeClass;

int main()
{
  std::unique_ptr<SomeClass> ptr;
}

错误:

In file included from /opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/memory:81:0,
from <source>:1:
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = SomeClass]':
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:236:17:   required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = SomeClass; _Dp = std::default_delete<SomeClass>]'
<source>:7:30:   required from here
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:74:22: error: invalid application of 'sizeof' to incomplete type 'SomeClass'
static_assert(sizeof(_Tp)>0,
^
Compiler exited with result code 1

这里同样的问题(证明跟继承无关):

#include <memory>

class SomeClass;

class NotDerived
{
//    ~NotDerived(); //defined in cpp

    std::unique_ptr<SomeClass> ptr;
};

int main(){
  NotDerived d;
}

错误:

In file included from /opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/memory:81:0,
from <source>:1:
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = SomeClass]':
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:236:17:   required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = SomeClass; _Dp = std::default_delete<SomeClass>]'
<source>:5:7:   required from here
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:74:22: error: invalid application of 'sizeof' to incomplete type 'SomeClass'
static_assert(sizeof(_Tp)>0,
^
Compiler exited with result code 1

现在让我们记住 unique_ptr 的真正含义:

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

还有 default_delete ...

在 ptr 上调用 delete

而指针上的delete(指向SomeClass)将要破坏Someclass,因此需要调用SomeClass::~SomeClass

您尚未声明的内容。因此出现错误。

为什么这很重要?

因为在你的代码中,如果你没有为Derived声明析构函数,会生成一个默认的,当然会调用ptr的析构函数。

此时,编译器将需要SomeClass 的完整定义,以便知道如何销毁它。

通过在 Derived 中声明析构函数,您可以将此问题推迟到 Derived::~Derived 的实现。

【讨论】:

  • 您已经描述了我链接到的答案是关于什么的,但您没有解决我的问题 - 为什么继承的构造函数会阻止智能指针成员中的前向声明类型。 Martin Bonner 提供的答案是正确的 - 因为编译器隐式定义了继承的构造函数,如默认构造函数(内联)因此问题。
  • @MichaelVlach 如果Derived 不是派生类,您会发现同样的问题。这与继承无关。
  • 我不会发现同样的问题,因为如果不继承另一个类,您就不可能继承构造函数。标题中甚至提到了这个问题......
  • @MichaelVlach 认为问题在于需要声明Derived 的析构函数?
  • 你为什么会这样想?关于需要声明Derived 的析构函数的问题是我在帖子中链接的问题。而我自己问为什么我不能在这种情况下使用继承的构造函数。标题说了这么多。也许您打算回答我链接的另一个问题? (它实际上适合那里)
【解决方案3】:

Derivated 没有声明构造函数,所以创建了默认构造函数,即内联。

默认构造函数或任何其他构造函数调用基构造函数的事实是另一个与非内联基构造函数无关的话题。

在 C++ 中,你的类的构造函数和基类中的构造函数之间基本上没有任何联系,除了基类中的构造函数在派生类中的构造函数之前执行,并且,如果不是显式的声明,将调用基类中的默认值。

【讨论】:

  • 我不认为你说的是​​正确的。当一个类继承构造函数时,它们与基类的构造函数具有明确和定义的关系。请参阅en.cppreference.com/w/cpp/language/…,尽管该链接也不完全正确。例如,它声明对于struct A { A(int); },任何派生类都将隐式声明默认构造函数struct B : public A { using A::A; },但事实并非如此。构造函数已声明但被隐式删除(VS 2015)。问题在于编译器定义继承构造函数的方式。
猜你喜欢
  • 2011-06-14
  • 1970-01-01
  • 2017-10-20
  • 2012-02-10
  • 1970-01-01
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多