默认构造函数不同声明的链接器错误

Linker error for different declarations of default constructors

本文关键字:链接 错误 声明 构造函数 默认      更新时间:2023-10-16

我一直在玩默认的构造函数,并注意到一个奇怪的行为(从我的角度来看(。

当我声明A() = default时,我没有链接器错误。

struct A
{
  int a;
  A() = default;
};
A a; // no linker error

但是,当我声明A();时,我得到它。

struct A
{
  int a;
  A();
};
A a; // linker error - undefined reference to `A::A()`

问题

  1. 两者之间有什么区别?

  2. ,如果A();产生链接器错误,为什么首先支持它?有任何实际应用吗?

更新(后续问题(

对于A();,如果用户未指定定义,为什么编译器隐式定义它?

在第一种情况下,编译器本身定义了构造函数。考虑到在第一种情况下,构造函数声明是相同的定义。

在第二种情况下,由于构造函数是用户定义的构造函数,因此必须定义构造函数的用户。在第二种情况下,只有声明构造函数。

考虑到您可以声明构造函数,然后使用指定符default

定义它。

例如

#include <iostream>
struct A
{
    int a;
    A();
};
A::A() = default;
int main() 
{
    A a;
    return 0;
}

写作的目的

A();

是向编译器声明,即(您!(将对A((的定义(由您!(进行给出,如果在另一个编译单元(CPP文件(中给出了链接器,则链接器负责找到此定义。

A() = default;

是向编译器声明的一种方式,即它应该为构造A自动构造时应采取的措施的定义,但要遵守语言规则。因此,由于已经给出了定义,链接器不会抱怨找不到它。

为什么首先不定义A((声明?因为您希望能够独立编译不同的CPP文件。否则,即使其中99%没有更改,您将始终必须编译所有代码。

A A A.CPP的构造很可能是定义的。如果您已经完成了结构A的设计,则理想情况下," A.CPP"将被汇编一次,再也不会再编译。如果您在不同类/结构/编译单元" b.cpp"中构造A,则编译器可以在编译" B.CPP"时信任A((的定义,而不知道定义实际上是什么样子。

关于后续问题,"为什么不能隐式地定义它":这可能是关于为什么发生错误的误解。编译器/链接器错误不会惩罚您。他们并不意味着编译器假装不做某件事,尽管可以做任何事情。出现错误是为了提醒您,您正在违反自己的承诺,以及编译器或链接者对此进行修复的任何尝试,可能是可能的,但很可能不会像您想要的那样工作,因为有一些迹象表明您已经失去了自己的要求。

话虽如此,可以隐含地定义一个((。,但是如果您写" A((;"您明确地告诉编译器不要隐式地执行此操作,并告诉链接器提醒您,如果您应该忘记定义它。这不仅适用于构造函数,而且适用于每种方法,大多数方法对定义它们的含义没有什么意义。" a.makemoney"的默认定义是什么?它是非平凡的,并且通过写A.Makemoney((;您告诉编译器:"相信我,我将在某个地方定义如何完成"。

(15.1构造函数(

X类的默认构造函数是X类的构造函数 哪个不是函数参数包的参数都有一个 默认参数(包括没有否的构造函数的情况 参数(。如果没有用于X类的用户指定构造函数, 没有参数的非明显构造函数被隐式声明 如默认为(11.4(。隐式指定默认构造函数是一个 班级的内联公共成员。

目的的目的

A() = default;

是告诉编译器创建默认构造函数,就好像用户未定义构造函数一样。考虑这种情况

struct A
{
  int a;
  A(int v): a(v) {}
};
A a; // compiler error, no default constructor

如果用户声明了任何构造函数,则默认一个将消失。

通过将A() = default;添加到该声明中,您将允许以这种方式构造A类。它是显式默认(11.4.2(

函数定义的函数定义为form-boty属于form =默认值; 称为明确默认的定义。一个功能 明确违约应

(1.1( - 成为特殊的成员函数,

(1.2( - 具有相同声明的功能类型(除了可能 不同的参考问题,除了副本 构造函数或复制分配操作员,参数类型可能是 "引用非const t",其中t是成员的名称 函数的类(好像被隐式声明了,

(1.3( - 没有默认参数。

方法遵循与任何具有外部链接的功能相同的链接规则。如果您在班级主体中声明任何方法,并且您的代码是指该方法(如果是构造函数 - 通过创建此类的对象(,则必须在同一程序的任何编译模块中定义它,否则程序将不构建。

A::A();

添加更多信息

参考class.ctor/1.2:

class-name不得是打字名称。在构造函数中 声明,可选的DELL-SECIFIER-SEQ中的每个拆除式指示符 应为朋友,内联,明确或constexpr。 [示例:

struct S {
   S();      // declares the constructor
};
S::S() { }   // defines the constructor

- 结束示例]

因此,A();只是声明,而不是引起的定义:

undefined reference to `A::A()'
struct A
{
    A() = default;

在这里,您要说的是,应自动实现构造函数,即任何具有默认构造函数的类成员都将与其初始化,并且任何没有默认构造函数的类成员都将不专业化。<<<<<<<<<</p>


struct A
{
  A();

在这里,您是说您将实现默认构造函数。因此,您必须实施它。例如

A::A()
: a(42)
{
}

这是其他信息,是其他信息的补充。

有差异W.R.T.当我们使用诸如 defaulted undeclared 删除的术语时,特殊成员函数的上下文。

当特殊成员函数为:

  • undeclared :它不参与过载分辨率。要使用此功能,请不要声明特殊的成员函数。
  • 删除:它参与过载分辨率。如果在任何地方使用,它可以防止汇编。要使用此功能,关键字是delete
  • 默认:它参与过载分辨率。提供默认的编译器生成的代码作为定义。要使用此功能,关键字是default

霍华德·辛纳特(Howard Hinnat(在同一中有一个有趣的演讲,他解释了何时默认/删除/删除特殊成员函数-https://www.youtube.com/watch?v=vlinb2fgkkhk