为什么构造函数不能返回void表达式?

时间:2020-08-23 03:04:22

标签: c++ oop constructor return

以下示例不符合标准:

void f();

struct A {
    A() { 
        return f(); // should be replaced to `f(); return;`
    }
};

但是,当构造函数替换为返回void的函数时,这是合法的。

我知道这是标准所必需的,如下:

12.1构造函数

12不得为构造函数指定返回类型(甚至无效)。构造函数主体中的return语句不得指定返回值。

但是为什么?

2 个答案:

答案 0 :(得分:0)

constructor是一种特殊的方法,旨在初始化该类的新实例。在幕后并没有实际调用它们(请参阅this question),因此实际上任何返回都将是不准确的,因为实际上没有返回任何内容。这里的内容与void没什么不同,因为void是一种类型,并且在不返回的代码块中返回类型会造成语法混乱和误导。

此外,构造函数被称为initialization的一部分,并且仅以写入int n = 5的相同方式将参数的值写入内存部分,将值5写入内存块,即使用n时引用。

对于用户而言,初始化过程似乎只是一个函数调用,而实际上却是一个完全不同的过程。

答案 1 :(得分:-2)

您说过:

但是,当构造函数替换为返回void的函数时,这是合法的。

但是,我不相信这是:这是 Compiler Explorer 的链接,它表明您的代码无法编译。

我已经使用x86-64 clang 10.0.1x86-64 gcc 10.2x64 msvc v19.24对此进行了测试,但全部三个都无法编译。以下是它们各自报告的错误:

  • 铛:-错误:构造函数A不能返回空表达式
  • 突出显示在return关键字下的位置。
  • gcc:-错误:从构造函数返回值
  • 突出显示的值低于0的地方。
  • msvc:-错误C2534:A构造函数无法返回值
  • 突出显示在该构造函数的整个代码行下。

您的问题中的这一说法似乎有点误导...我完全不知道这是合法的!

构造函数只能做三件事...

  • 早归
  • 引发异常
  • 在构造函数的块或作用域的末尾};之后返回,并将控制流返回给调用者。

所有函数都必须具有返回类型,即使是那些不返回的函数,例如:

  • void print(){/*...*/ return;}
  • int add(int a, int b) { return (a+b); }
  • 等...

但是,constructorsdestructors从未用type声明。

您将永远不会看到:

struct A{
    void A() { return; }  // Fails to compile
    int A() { return 0; } // Fails to compile
    void ~A() { return; } // Fails to compile
    int ~A() {return 0; } // Fails to compile
};

有{strong> NO 个typectordtor相关,至少在c++中没有。

现在,如果您从 Compiler Explorer 中删除代码中的(void)0,您将看到foo():bar():标签及其堆栈框架。您还将看到main:标签及其堆栈框架。然而,A您什么都看不到。现在,如果通过使用类对象的实例实例化实例来在main中添加A的实例,您将看到main的堆栈框架中汇编代码的更改,因为它是main()函数的局部变量

这是clang's程序集:

没有声明A a ...

foo():                                # @foo()
        push    rbp
        mov     rbp, rsp
        pop     rbp
        ret
bar():                                # @bar()
        push    rbp
        mov     rbp, rsp
        xor     eax, eax
        pop     rbp
        ret
main:                                   # @main
        push    rbp
        mov     rbp, rsp
        xor     eax, eax
        mov     dword ptr [rbp - 4], 0
        pop     rbp
        ret

已声明A a; ...

foo():                                # @foo()
        push    rbp
        mov     rbp, rsp
        pop     rbp
        ret
bar():                                # @bar()
        push    rbp
        mov     rbp, rsp
        xor     eax, eax
        pop     rbp
        ret
main:                                   # @main
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     dword ptr [rbp - 4], 0
        lea     rdi, [rbp - 8]
        call    A::A() [base object constructor]
        xor     eax, eax
        add     rsp, 16
        pop     rbp
        ret
A::A() [base object constructor]:                              # @A::A() [base object constructor]
        push    rbp
        mov     rbp, rsp
        mov     qword ptr [rbp - 8], rdi
        pop     rbp
        ret

这里是gcc的程序集:

没有:

foo():
        push    rbp
        mov     rbp, rsp
        nop
        pop     rbp
        ret
bar():
        push    rbp
        mov     rbp, rsp
        mov     eax, 0
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, 0
        pop     rbp
        ret

使用:

foo():
        push    rbp
        mov     rbp, rsp
        nop
        pop     rbp
        ret
bar():
        push    rbp
        mov     rbp, rsp
        mov     eax, 0
        pop     rbp
        ret
A::A() [base object constructor]:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        nop
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        lea     rax, [rbp-1]
        mov     rdi, rax
        call    A::A() [complete object constructor]
        mov     eax, 0
        leave
        ret

这里是msvc的程序集:

没有:

void foo(void) PROC                                        ; foo
        ret     0
void foo(void) ENDP                                        ; foo

int bar(void) PROC                                        ; bar
        xor     eax, eax
        ret     0
int bar(void) ENDP                                        ; bar

main    PROC
        xor     eax, eax
        ret     0
main    ENDP

使用:

void foo(void) PROC                                        ; foo
        ret     0
void foo(void) ENDP                                        ; foo

int bar(void) PROC                                        ; bar
        xor     eax, eax
        ret     0
int bar(void) ENDP                                        ; bar

this$ = 8
A::A(void) PROC                                  ; A::A, COMDAT
        mov     QWORD PTR [rsp+8], rcx
        mov     rax, QWORD PTR this$[rsp]
        ret     0
A::A(void) ENDP                                  ; A::A

a$ = 32
main    PROC
$LN3:
        sub     rsp, 56                             ; 00000038H
        lea     rcx, QWORD PTR a$[rsp]
        call    A::A(void)                     ; A::A
        xor     eax, eax
        add     rsp, 56                             ; 00000038H
        ret     0
main    ENDP

从所有3个编译器中都可以看到,当您没有对象实例时,就不会生成汇编代码。当您确实有一个对象的实例时,所有编译器都将call调用到A::A()A::A(void) ...执行控制就像函数一样进入这些构造函数,但是它们没有类型,因为它们不是实际功能,只是被当作一个...

它们确实具有一个堆栈框架,一个作用域和一个像函数一样的生存期,但是只有在声明classstruct类型的对象时才调用它们。 class构造函数的汇编指令仅在创建实例时生成。

它们不像常规函数,您可以在其中执行以下操作:

foo() {
    A(); // this is not valid
    A::A(); // this is not valid
} 

但是,这是有效的:

foo() {
    A a(); // Valid
}

constructor nameda type的对象上调用A

我希望这有助于阐明为什么constructorctor没有与之关联的返回类型。他们的destructordtor也是如此。


修改

我认为人们很想解释我想要得到的东西...我对代码做了些微修改:也许这可以更清楚地说明我的意图...

这是C ++代码:

struct A {
    //A() { return (void)0; }
    A() {};
};

void foo() {
    A a;
    return (void)0;
}

int bar() {
    A a;
    return 0;
}

int main() {
    foo();
    int baz = bar();
    A a;

    return 0;
}

这是GCC's的{​​{1}}版本:

Assembly

这是指向 Compiler Explorer udpated链接。而且,如果仔细查看生成的程序集,则在调用foo(): # @foo() push rbp mov rbp, rsp sub rsp, 16 lea rdi, [rbp - 8] call A::A() [base object constructor] add rsp, 16 pop rbp ret A::A() [base object constructor]: # @A::A() [base object constructor] push rbp mov rbp, rsp mov qword ptr [rbp - 8], rdi pop rbp ret bar(): # @bar() push rbp mov rbp, rsp sub rsp, 16 lea rdi, [rbp - 8] call A::A() [base object constructor] xor eax, eax add rsp, 16 pop rbp ret main: # @main push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 call foo() call bar() mov dword ptr [rbp - 8], eax lea rdi, [rbp - 16] call A::A() [base object constructor] xor eax, eax add rsp, 16 pop rbp ret 时,将没有信息,也没有任何类型的程序集代码。调用A::A()时,其返回类型foo()已被优化,而调用void时,将使用汇编代码存储其返回值。