空析构函数与字面值析构函数的区别

16

考虑以下代码:

#include <iostream>

class Test
{
    public:
        constexpr Test(const int x) : _x(x) {}
        constexpr int get() const {return _x;}
        ~Test() {} // HERE
    protected:
        const int _x;
};

int main()
{
    static constexpr Test test(5);
    return 0;
}

如果我删除HERE这一行,代码就可以编译通过,但是如果我定义一个空析构函数,就会产生编译错误,说Test是非字面的。

为什么会这样,并且空析构函数和没有析构函数有什么区别?

编辑:另一个相关问题:如果空的字面值析构函数与非空析构函数不同,如何定义一个受保护的字面值析构函数?


1
在一个constexpr对象中,您不能拥有非平凡的析构函数。 - chris
我需要它们用于一些抽象的CRTP类。 - Vincent
6
我认为你可以使用 ~Test() = default; - Xeo
1
@Vincent 如果是这样的话,为什么不将构造函数设为protected,而不是析构函数呢? - Konrad Rudolph
1
如果某个类被设计成其他类的基类,那么它的析构函数应该是虚拟的或者受保护的,以防止通过基指针删除派生对象时出现未定义行为。 - Tadeusz Kopec for Ukraine
显示剩余6条评论
2个回答

20

n3376中的引用:

7.1.5/9

constexpr修饰符在对象声明中使用时将对象声明为const。这种对象应具有字面类型并应进行初始化。如果通过构造函数调用进行初始化,则该调用必须是常量表达式

3.9/10

如果一个类型具有平凡析构函数,那么它是一个字面类型...

12.4/5

如果析构函数不是虚拟的,并且:

—— 该类的所有直接基类具有平凡析构函数,并且

—— 该类中所有非静态数据成员的类类型(或其数组)都具有平凡析构函数,则析构函数是平凡的。

否则,析构函数是非平凡的。

Clang诊断信息更加详细:

error: constexpr variable cannot have non-literal type 'const C'

'C' is not literal because it has a user-provided destructor

2
Clang:1254,其他人:32 - chris
1
@chris:抱歉我不太懂,你的评论是什么意思? - user1284631
@axeoth,这是一个玩笑,关于Clang在错误提示方面相比其他编译器更易读和/或更有帮助。 - chris

0

没有析构函数会导致编译器添加一个平凡的析构函数,在规范中定义不明确,但基本上什么都不做。

如果你指定了一个析构函数,它就不会添加平凡的析构函数。你的析构函数是非平凡的。

在你的情况下,Test::~Test() { }看起来非常简单,但这只是人类对你所看到的东西的解释。进一步说,还有:

Test::~Test()
{
    int a = 5;
}

我们可以看到优化器可以优化掉a,因此它显然什么也没做。那么怎么样呢:
Test::~Test()
{
    for (int i = 0; i < 1000; i += 2) {
        if ((i % 2) == 1)
            doSomeSideEffect(); // like throwing or setting a global
    }
}

我们可以看到,i 永远不可能是奇数,因此析构函数什么也不做。
规范必须定义什么可以成为常量表达式,什么不能。他们并没有深入定义“什么也不做”,而是简单地声明,唯一好用于 constexpr 的什么也不做析构函数是编译器提供的平凡析构函数。

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