why does default constructor being outside class or inside class make a difference in whether the class is POD?

huangapple go评论59阅读模式
英文:

why does default constructor being outside class or inside class make a difference in whether the class is POD?

问题

在以下代码中,为什么 POD 被视为 POD,而 notPOD 被视为非 POD?它们之间唯一的区别是默认构造函数的定义是放在类内还是类外。我一直以为它们的作用是一样的,但显然不是这样。

    #include <iostream>
    struct POD {
        POD() = default;
    };
    struct notPOD {
        notPOD();
    };
    notPOD::notPOD() = default;
    int main() {
        std::cout << std::boolalpha << std::is_pod<POD>() << ' ' << std::is_pod<notPOD>() << '\n';
    }
英文:

In the following code, why is POD considered POD while notPOD considered not POD? The only difference between them is whether the definition for defaulted default constructor is placed inside or outside the class. I always thought that both do the same thing but it's apparently not the case.

#include &lt;iostream&gt;
struct POD {
    POD() = default;
};
struct notPOD {
    notPOD();
};
notPOD::notPOD() = default;
int main() {
    std::cout &lt;&lt; std::boolalpha &lt;&lt; std::is_pod&lt;POD&gt;() &lt;&lt; &#39; &#39; &lt;&lt; std::is_pod&lt;notPOD&gt;() &lt;&lt; &#39;\n&#39;;
}

答案1

得分: 14

首先,术语“POD类型”已经过时,而std::is_pod自C++20以来已经弃用。这个概念已经分成了多个更具体的概念,更适用于POD类型所期望的特定行为。特别是新的概念是“平凡类型”和“标准布局类型”。

你问题中的关键是这个类是否是“平凡”的。如果一个类是“平凡的”,那么它是“平凡可复制的”,并且至少有一个合格的默认构造函数,而且所有这些构造函数都是“平凡的”。

一个默认构造函数只有在它“不做任何事情”(非正式说法)并且不是“用户提供的”时才是“平凡的”。在你的示例中,无论在哪种情况下,默认构造函数都没有做任何事情,但在第一个示例中它不是“用户提供的”,而在第二个示例中它是“用户提供的”。如果构造函数不是隐式声明的(即用户实际编写了它的声明)并且在其第一次声明中没有默认值,那么它就是“用户提供的”。这是你两个示例之间的区别。

这个规则的原因是所有的翻译单元都必须同意默认构造函数和类是否是平凡的。与这个类型特性相关的语言规则在不同的翻译单元中不能不同。

如果构造函数在类内部被默认化,那么所有的翻译单元都必须同意这一点,因为具有类定义的翻译单元也必须在其声明中将构造函数默认为默认值。不同翻译单元中的类定义必须在记号上是相同的,否则这些定义将违反单一定义规则,程序将无效。

如果你在类外默认构造函数,那么只有一个默认构造函数的翻译单元会看到它。另一个翻译单元可以包含类的定义,但不需要包含构造函数的默认定义,因此它可能不知道它是否被默认化。因此,无法确定构造函数是否在每个翻译单元中都是用户提供的,特别是另一个翻译单元可能不知道构造函数实际上“不做任何事情”。因此,它不能被视为平凡的。

换句话说,这个特性必须仅依赖于类定义,以保持在不同翻译单元之间的一致性。它不能依赖于类定义之后的额外声明。因此,规则是这样的:这样的构造函数和类不是平凡的。

英文:

First, the term "POD type" is out-dated and std::is_pod is deprecated since C++20. The concept has been separated into multiple more specific concepts that are more applicable to specific behaviors one expects of a POD type. In particular the new concepts are trivial types and standard-layout types.

The issue in your question is whether the class is trivial. A class is trivial if it is trivially-copyable and has at least one eligible default constructor and all such constructors are trivial.

A default constructor is trivial only if it, informally speaking, "doesn't do anything", and it is not user-provided. In your example the default constructor doesn't do anything in either case, but it is not user-provided in the first example, while it is in the second one. A constructor is user-provided if it is not implicitly-declared (i.e. the user has actually written its declaration) and it is not defaulted on its first declaration, i.e. inside the class, which is the difference between your two examples.

The reason for this rule is that all translation units must agree on whether or not the default constructor and the class are trivial. There are language rules attached to this type trait that must not be different in different translation units.

If the constructor is defaulted inside the class, then all translation units must agree on that, because a translation unit which has the definition of the class must also have the constructor defaulted in its declaration. Definitions of classes in different translation units must be token-identical, otherwise the definitions would violate the one-definition rule and the program wouldn't be valid.

If you default the constructor outside the class, then only the one translation unit with the defaulted constructor will see it. Another translation unit may include the class definition, but does not need to include the defaulted definition of the constructor and so it may not know that it is defaulted. Then it is impossible to say whether the constructor is user-provided in every translation unit and in particular the other translation unit may not know whether the constructor actually "doesn't do anything". So it can't be considered trivial.

In other words, the trait must depend only on the class definition in order to be consistent between translation units. It can't depend on additional declarations after the class definition. So, the rule is that such a constructor and class are not trivial.

答案2

得分: 4

因为构造函数notPOD::notPOD()在其第一个声明中没有被默认生成,这意味着notPOD具有一个用户提供的构造函数,这暗示notPOD具有一个非平凡的默认构造函数,进而意味着notPOD不是一个平凡类。这进一步意味着notPOD不是一个POD类。以下是详细描述。

POD类中:

POD类是一个既可以是POD结构体又可以是POD联合体的类。

现在,POD结构体的定义如下:

POD结构体是一个非联合类,既是平凡类又是标准布局类,并且没有非POD结构体、非POD联合体(或这些类型的数组)的非静态数据成员。

notPOD没有满足成为平凡类的第二个要求,而POD则满足。来自平凡类的定义如下:

平凡类是具有平凡默认构造函数([class.ctor])且可平凡复制的类。

因此,接下来我们来看平凡默认构造函数

默认构造函数是平凡的,如果它不是用户提供的 并且如果:

最后,关键在于构造函数notPOd::notPOD()是否被用户提供

如果特殊成员函数在其第一个声明时是用户声明的并且没有在其第一个声明中显式地默认生成或删除,则它是用户提供的。

这意味着POD::POD()不是用户提供的,因此它是一个POD类,而notPOD::notPOD()是用户提供的,因为它在其第一个声明中没有被默认生成,所以notPOD不是一个POD类。

英文:

TLDR;

> why is POD considered POD while notPOD considered not POD?

Because the constructor notPOD::notPOD() is not defaulted in its first declaration which means notPOD has a user-provided constructor implying that notPOD has a non-trivial default constructor which in turn means that notPOD is not a trivial class. This further means that notPOD is not a POD class. This is described in detail below.


From POD class:

> A POD class is a class that is either a POD struct or a POD union.

Now, a POD struct is defined to be:

> A POD struct is a non-union class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types).

(emphasis mine)

But the second requirement of being a trivial class is not fulfilled by notPOD but fullfilled by POD. From trivial class:

> A trivial class is a class that has a trivial default constructor ([class.ctor]) and is trivially copyable.

So next we come onto trivial default constructor:

> A default constructor is trivial if it is not user-provided and if:

Finally it comes down to whether the constructor notPOd::notPOD() is user-provided:

> A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.

(emphasis mine)

This means that POD::POD() is not user-provided and so it is a POD class while notPOD::notPOD() is user-provided since it is not defaulted at its first declaration so notPOD is not a POD class.

huangapple
  • 本文由 发表于 2023年5月28日 17:15:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76350757.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定