当使用类字段作为参数时,委派构造函数会导致分段错误。

10

实际上,段错误是发生在我尝试编译的另一个程序中,这是由于这种行为引起的。

我的问题是:

这是一个 bug 还是我的问题?

可以以任何方式复现(即使 something 字段是私有的或受保护的),这里是我的示例:

main.cc:

#include <iostream>
class Test {
    public:
        const char* something = "SOMETHING HERE!!!";
        Test(const int& number) : Test(something, number) { }
        // XXX: changed `something` to `_something` to make it different
        Test(const char* _something, const int& number) {
            std::cout << _something << std::endl;
            std::cout << number << std::endl; }
        ~Test() { }
};

int main(int argc, char* argv[]) {
    Test te1(345);
    Test te2("asdasdad", 34523);
    return 0;
}

这是使用以下方式进行编译时所发生的情况:

g++ main.cc -Os -o main

并且运行:

./main

输出结果为:

pi@pi:~/ $ ./main
A"�~ <-- this is random
345
asdasdad
34523

但是当我使用-O0-O1-O2等优化时,输出只有一个换行符:

pi@pi:~/ $ ./main
pi@pi:~/ $

G++ 版本:

pi@pi:~/ $ g++ --version
g++ (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516

在下一行 const char* something = "SOMETHING HERE!!!"; 中,你是指 _something 吗?如果不是,那么这个变量就没有被设置。 - Mixhab
一切都如我在问题描述中所解释的那样。 - Memos Electron
相当有趣。 - David G
2个回答

20
const char* something = "SOMETHING HERE!!!";
右侧的默认初始化程序,顾名思义,仅在您未在构造函数的初始化器列表中提供显式初始化程序时使用。让我们看看你的:

右侧的默认初始化程序,顾名思义,仅在您未在构造函数的初始化器列表中提供显式初始化程序时使用。让我们看看你的:

Test(const int& number) : Test(something, number) { }

好的,我们正在委托给另一个构造函数。那个构造函数将执行完整的初始化,因此不使用默认初始化器。但是......我们将未初始化的something值作为参数传递。

Test(const char* _something, const int& number) { /* ... */ }

咦,现在我们试图使用_something的值,它是something的一个副本,并且是不确定的。这将导致未定义行为和火灾。

如果你没有无限耐火的鸡和蛋,你真的不应该将类成员的值作为参数传递给其构造函数。


您可以通过在委托构造函数的调用中放置默认值来获得所需的行为:

Test(const int& number) : Test("SOMETHING HERE!!!", number) { }

...或将其保留在专用静态变量中:

static constexpr char *const defaultSomething = "SOMETHING HERE!!!";
Test(const int& number) : Test(defaultSomething, number) { }

1
另外,一个私有的默认构造函数可以让你这样写:Test(const char* _something, const int& number) : Test() { /* ... */ } - Davislor
1
你真的不应该将类成员的值作为参数传递给它的构造函数,除非你有无限的防火鸡和蛋。——干得好 - sehe

14

这是一个bug还是我的错?

哦,这是你的错。默认成员初始化器仅用于在非委托构造函数中初始化成员对象。根据 [class.base.init]/9,重点在我:

在非委托构造函数中,如果给定的可能构建的子对象没有被 mem-initializer-id 指定(包括没有 ctor-initializer 的情况,因为构造函数没有 ctor-initializer),则

  • 如果该实体是具有默认成员初始化器的非静态数据成员,并且满足以下任一条件:[...] 该实体将从其默认成员初始化器中指定的值初始化,如 [dcl.init] 中所述;

因此,当您将其传递给目标构造函数时,something 就没有被初始化。您的程序具有未定义的行为,并会崩溃。


5
@Quentin - 好吧,楼主确实有问 :P - StoryTeller - Unslander Monica
谢谢!我接受了@Quentin的答案,因为它更完整。 - Memos Electron
6
没关系,我投了同样的票 :) 还有提到防火鸡的那个... 我真应该再进货一些了。 - StoryTeller - Unslander Monica

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