3

假设我有一个公共类和一个私有实现类(例如 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<PrivateClass>::~demo_ptr被 实例化而main.cpp无法编译,并且该析构函数无法编译,因为它不能sizeof在不完整的类型上执行PrivateClass. 显然,编译器~demo_ptr在 消费中实例化是没有用的main.cpp,因为它永远无法生成合理的实现(看如何~PrivateClass是不可访问的)。(PublicClass.cpp在所有经过测试的编译器上都能正常编译。)

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

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

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

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

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

更新:这是由于编译器错误,正如 Andy Prowl 所指出的,该错误在 C++ Builder XE8 中仍未修复。我已向 Embarcadero 报告了该错误: bcc32 编译器在使用带有 PIMPL 习惯用法的 std::auto_ptr 时导致未定义的行为,因为模板实例化规则不遵循 C++ 规范

4

2 回答 2

5

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

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

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

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

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

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


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

于 2013-04-09T00:07:19.897 回答
1

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复制构造函数,因此可以解决您的问题。

于 2013-04-09T00:14:47.883 回答