【问题标题】:Is it valid for a C++ compiler to implicitly instantiate ALL member functions of a template class?C++ 编译器隐式实例化模板类的所有成员函数是否有效?
【发布时间】:2013-03-31 05:50:48
【问题描述】:

假设我有一个公共类和一个私有实现类(例如 PIMPL 模式),我希望用一个带有检查删除的模板智能指针类包装私有类,如下所示:

PublicClass.h

class PrivateClass;

// simple smart pointer with checked delete
template<class X> class demo_ptr
{
public:
    demo_ptr (X* p) : the_p(p) { }
    ~demo_ptr () {
        // from boost::checked_delete: don't allow compilation of incomplete type
        typedef char type_must_be_complete[ sizeof(X)? 1: -1 ];
        (void) sizeof(type_must_be_complete);
        delete the_p;
    }
private:
    X* the_p;
};

// public-facing class that wishes to wrap some private implementation guts
class PublicClass
{
public:
    PublicClass();
    ~PublicClass();

private:
    demo_ptr<PrivateClass> pvt;
};

PublicClass.cpp

#include "PublicClass.h"

class PrivateClass
{
public:
    // implementation stuff goes here...
    PrivateClass() {}
};
//---------------------------------------------------------------------------
PublicClass::PublicClass() : pvt(new PrivateClass()) {}

PublicClass::~PublicClass() {}

main.cpp

#include "PublicClass.h"

int main()
{
    PublicClass *test = new PublicClass();
    delete test;
    return 0;
}

此代码在 Visual C++ 2008 上编译成功,但在旧版本的 C++ Builder 上编译失败。特别是,main.cpp 无法编译,因为demo_ptr&lt;PrivateClass&gt;::~demo_ptr 正在由main.cpp 实例化,并且该析构函数无法编译,因为它不能对PrivateClass 的不完整类型执行sizeof。显然,编译器在消费main.cpp 中实例化~demo_ptr 是没有用的,因为它永远无法生成合理的实现(看看~PrivateClass 是如何不可访问的)。 (PublicClass.cpp 在所有经过测试的编译器上都能正常编译。)

我的问题是:C++ 标准对模板类的成员函数的隐式实例化有何规定?可能是以下之一?特别是,这些年来这种情况发生了变化吗?

  • 如果使用模板类,那么该类的所有成员函数都应该被隐式实例化 - 无论是否使用?
  • 或者:如果实际使用,模板类函数一次只能隐式实例化一个。如果未使用特定模板类函数,则不应隐式实例化它 - 即使使用和实例化了其他模板类函数。

似乎很明显,第二种情况是 today 的情况,因为 PIMPL 和 unique_ptr 使用了相同的模式及其检查删除,但也许过去不是这种情况?过去第一种情况是可接受的编译器行为吗?

或者换句话说,编译器是否存在错误,或者它是否准确地遵循了 C++98 标准,并且标准多年来发生了变化?

(有趣的事实:如果您在 C++ Builder 中删除选中的删除,并关闭函数内联,项目将顺利编译。PublicClass.obj 将包含正确的 ~demo_ptr 实现,main.obj 将包含不正确的~demo_ptr 具有未定义行为的实现。使用的函数将取决于将这些文件提供给链接器的顺序。)

更新:这是由于编译器错误,正如 Andy Prowl 所指出的,该错误在 C++ Builder XE8 中仍未修复。我已经向 Embarcadero 报告了这个错误: bcc32 compiler causes undefined behavior when using std::auto_ptr with PIMPL idiom because template instantiation rules do not follow C++ spec

【问题讨论】:

  • BTW demo_ptr 违反了第 3 条规则——你需要在它被双重删除之前解决这个问题。
  • “旧编译器”以及您所说的症状:可能是编译器错误。据我所知,没有一个 C++ 编译器完全符合语言规范。最新版本的 clang 可能是您最好的选择,但请理解,如果您使用的是非常老的编译器,您可能会遇到非常老的和错误的行为。
  • @BenVoigt:理解;为了简洁起见,这只是一个迂腐的测试用例。实际上我只是使用 auto_ptr。
  • 你为什么用auto_ptr
  • @Yakk:代码已经广泛使用它,运行时库还不支持unique_ptr。我们总有一天会换的。

标签: c++ windows c++11 c++builder


【解决方案1】:

如果使用了模板类,那么该类的所有成员函数都应该被隐式实例化——无论是否使用?

,绝对不是这样。根据 C++11 标准的第 14.7.1/10 段(和 C++03 标准的第 14.7.1/9 段)非常明确地规定:

实现不应隐式实例化函数模板、成员模板、非虚拟 成员函数、成员类或不需要实例化的类模板的静态数据成员。

至于何时需要实例化,第 14.7.1/2 段规定:

除非类模板或成员模板的成员已被显式实例化或显式 特化,成员的特化被隐式实例化当特化被引用时 在需要成员定义存在的上下文中; [...]

如果从不引用成员函数,情况肯定不是这样。


不幸的是,我无法提供有关 C++03 之前规则的官方参考,但据我所知,C++98 中采用了相同的“惰性”实例化机制。

【讨论】:

  • 成员函数肯定是从PublicClass::~PublicClass 的自动生成部分引用的,因为它调用成员析构函数,例如demo_ptr::~demo_ptr
  • @BenVoigt:我没有说没有引用成员函数。我只是回答了关于标准要求什么的问题:“如果从不引用成员函数,肯定不是这种情况。”
  • 好的,很高兴您能理解。我仍然认为澄清对其他读者有用。
  • 正是我在标准引文中寻找的答案。谢谢。
  • @JamesJohnston:很高兴它有帮助:)
【解决方案2】:

unique_ptr 是一只有趣的野兽。

不像auto_ptr从智能指针析构函数中调用被引用对象的析构函数,unique_ptr::~unique_ptr仅仅调用了一个之前存储的删除函数。

删除函数存储在unique_ptr 构造函数中,每个PublicClass 构造函数都会调用它。用户定义的构造函数是在PrivateClass::~PrivateClass 可用的上下文中定义的,所以没关系。但是其他隐式生成的PublicClass 构造函数呢,比如移动构造函数呢?它们是在使用点生成的;他们还需要初始化unique_ptr 成员,这意味着他们必须提供一个删除器。但是没有PrivateClass::~PrivateClass,他们就做不到。

等等,你的问题提到了unique_ptr,但你的代码没有使用它。奇怪...

即使从智能指针析构函数调用析构函数,它仍然可以从包含类构造函数 中使用 ODR。这是为了异常安全——构造函数需要能够拆除部分构造的类,其中包括成员的销毁。

看起来 C++Builder 可能正在生成 PublicClass 副本或更多构造函数,即使您的程序不使用它。这并不违反安迪提到的规则,因为PublicClass 不是模板。我认为编译器有权在处理类定义时为PublicClass 生成默认的复制构造函数,因为您不能为默认成员提供类外定义。尊重demo_ptr 的三规则将排除PublicClass 具有复制构造函数,因此可能会解决您的问题。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-16
  • 2013-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多