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

时间:2018-04-02 07:03:37

标签: c++ c++11 c++14

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

当我声明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();产生链接器错误,为什么首先支持它?任何实际应用?

  3. 更新(跟进问题)

    对于A();,如果用户没有指定定义,为什么不能由编译器隐式定义?

6 个答案:

答案 0 :(得分:4)

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

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

考虑到您可以声明构造函数,然后使用说明符"query": "/Achieved by [^@]*@/" 定义它。

例如

default

答案 1 :(得分:3)

写作的目的

A();

是向编译器声明A()实际应该做的定义将在其他地方给出(由你!),如果在另一个编译单元(cpp-file)中给出,则链接器负责找到这个定义。

A() = default;

是一种向编译器声明它应该根据语言规则自动创建应该在构造A时应该做什么的定义的方法。因为已经给出了定义,链接器不会抱怨没有找到它。

为什么在没有首先定义A()的情况下声明是否支持?因为您希望能够独立编译不同的cpp文件。否则,您将始终必须编译所有代码,即使99%的代码没有更改。

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

关于后续问题,"为什么无法隐式定义":这可能是对错误发生原因的误解。编译器/链接器错误不会发生惩罚。它们并不意味着编译器假装不能做某事尽管它可以。出现错误是为了提醒您违反自己的承诺以及编译器或链接器修复它的任何尝试都可能,但很可能不会像您希望的那样工作,因为有迹象表明你已经忘记了自己的要求。

话虽如此,A()可以被隐含地定义。 但如果你写'#34; A();"你明确地告诉编译器不要隐式地做,并告诉链接器提醒你,你是否应该忘记定义它。这不仅适用于构造函数,而是适用于各种方法,大多数人都没有自然意识到隐含地定义它们意味着什么。 " A.makeMoney&#34 ;?的默认定义是什么?通过编写A.makeMoney(),这是非常重要的;你告诉编译器:"相信我,我将在某处定义它将如何完成"。

答案 2 :(得分:1)

(15.1构造函数)

  

类X的默认构造函数是类X的构造函数   哪个不是函数参数包的参数都有   默认参数(包括没有的构造函数的情况)   参数)。 如果类X没有用户声明的构造函数,则a   隐式声明没有参数的非显式构造函数   作为默认值(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)

  

函数定义,其函数体的形式为= default;   被称为明确默认的定义。一个功能   明确默认

     

(1.1) - 是一个特殊的成员函数,

     

(1.2) - 具有相同的声明函数类型(可能除外)   不同的参考资格,除了复制的情况   构造函数或复制赋值运算符,参数类型可以是   “引用非const T”,其中T是成员的名称   函数的类)就像它已被隐式声明一样,并且

     

(1.3) - 没有默认参数。

方法遵循与具有外部链接的任何函数相同的链接规则。如果在类体中声明任何方法,并且您的代码引用该方法(在构造函数的情况下 - 通过创建此类的对象),您必须在同一程序的任何编译模块中定义它,否则程序将是格式错误的。

答案 3 :(得分:0)

struct A
{
    A() = default;

在这里,您要说构造函数应该自动实现,即任何具有默认构造函数的类成员都将使用它进行初始化,并且任何没有默认构造函数的类成员都将是没有初始化。

struct A
{
  A();

在这里,您说您将实现默认构造函数。所以你必须实现它。 E.g。

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

答案 4 :(得分:0)

这是其他信息,是其他人提供的答案的补充。

w.r.t.当我们使用诸如默认未声明已删除等术语时,特殊成员函数的上下文。

当特殊成员函数是:

  • 未声明:它不参与重载解析。要使用此功能,请不要声明特殊成员函数。
  • 已删除:它参与了重载解析。如果在任何地方使用它,它会阻止编译。要使用此功能,关键字为delete
  • 默认:它参与重载解析。默认的编译器生成的代码作为定义提供。要使用此功能,关键字为default

霍华德·欣纳特(Howard Hinnat)有一个有趣的讲话,他解释了特殊成员函数何时被默认/删除/未宣布 - https://www.youtube.com/watch?v=vLinb2fgkHk

答案 5 :(得分:0)

A::A();

添加更多信息

参考class.ctor/1.2

  

类名不应是typedef-name。在构造函数中   声明,可选的decl-specifier-seq中的每个decl-specifier   应该是朋友,内联,明确或constexpr。   [实施例:

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

S::S() { }   // defines the constructor
     

- 结束示例]

因此,A();只是一个声明而不是导致的定义:

undefined reference to `A::A()'