【问题标题】:Using default value for const reference argument causes crash使用 const 引用参数的默认值会导致崩溃
【发布时间】:2018-07-05 23:05:25
【问题描述】:

我有一些这样的代码:

struct Data {
    Data(const std::vector<int> &data = {}) : data_(data) {}
    const std::vector<int> &data_;
};

Data create1() {
    return Data(); // bad
}

Data create2() {
    return Data({}); // bad
}

Data create3(const std::vector<int> &data = {}) {
    return Data(data); // good
}

Data create4() {
    static const std::vector<int> data;
    return Data(data); // good
}

void main() {
    auto data1 = create1(); // deleted data_
    auto data2 = create2(); // deleted data_
    auto data3 = create3(); // ok
    auto data4 = create4(); // ok
}

四个创建函数在我看来是一样的。但是为什么 create1 和 create2 会导致删除 data_ 而 create3 和 create4 都可以呢?

【问题讨论】:

  • 您正在存储对临时的引用。那是不行的。将您的构造函数更改为Data(std::vector&lt;int&gt;&amp; data); 以避免这种情况。
  • 什么是void main()
  • 在标准 C++ 中非法,@scohe001,但我很确定你知道这一点。
  • @alter igel 但是为什么不将 create3 视为临时对象?

标签: c++ reference default-arguments


【解决方案1】:

前两个案例很明显是滥用临时工。根据提问者的 cmets,他们已经了解了 create1create2 的问题所在,所以让我们关注 create3 及其工作原理。

剧透:它没有。

我将对代码进行一些改动,以使正在发生的事情更加明显。首先,我们用一个简单的类替换vector,让我们更好地了解构造和破坏。

struct test
{
    test()
    {
        std::cout << "test ctor\n";
    }
    ~test()
    {
        std::cout << "test dtor\n";
    }
};

现在我们做一些类似于Data 的事情,让它使用test 而不是vector

struct Data {
    Data(const test &data = {}) : data_(data) 
    {
        std::cout << "data ctor\n";
    }
    ~Data()
    {
        std::cout << "data dtor\n";
    }
    const test &data_;

    void forceuse() // just to make sure the compiler doesn't get any bright ideas about 
                    // optimizing Data out before we're through with it.
    {
        std::cout << "Using data\n";
    }
};

我们为create3 添加了一些额外的诊断信息,并再次将vector 替换为test

Data create3(const test &data = {}) {
    std::cout << "in create3\n";
    return Data(data); // good
}

main做同样的事情

int main() {
    { // change the scope of data3 so that we can put a breakpoint at the end of main 
      // and watch all of the fun
        std::cout << "calling create3\n";
        auto data3 = create3(); // ok
        std::cout << "returned from create3\n";
        data3.forceuse();
    }
}

这个输出是

calling create3
test ctor
in create3
data ctor
test dtor
returned from create3
Using data
data dtor

test 在调用create3 期间创建,并在退出create3 时销毁。它在main 中不存在。如果它似乎在main 中还活着,那只是愚蠢的倒霉。你的朋友和我的未定义行为是个混蛋。再次。

testData 之前创建,但也在Data 之前销毁,使Data 处于不良状态。

上面的代码都很好地组装并在在线 IDE 中运行:https://ideone.com/XY30XH

【讨论】:

  • 我认为当向量在调试模式下被销毁时,它会将内部指针设置为 null 或其他东西,但它似乎并没有以这种方式工作。所以即使在调试模式下,被破坏的向量仍然可以正常工作。现在我知道默认参数不是静态的。
  • 在 C++ 中,最好假设最坏的情况,并且即使在调试模式下,也不要指望你没有明确要求的牵手。我个人不喜欢调试器更改行为以使某些错误更明显,因为它隐藏了进程中的其他错误。
【解决方案2】:

创建静态数据成员并将其用于初始化:

struct Data {
    Data(const std::vector<int> &data = empty_) : data_(data) {}
    const std::vector<int> &data_;
private:
    static std::vector<int> empty_;
};

std::vector<int> Data::empty_ = {};

Data create1() {
    return Data(); // good
}

Data create2() {
    return Data({}); // still bad, as it should be
}

Data create3(const std::vector<int> &data = {}) {
    return Data(data); // good
}

Data create4() {
    static const std::vector<int> data;
    return Data(data); // good
}

void main() {
    auto data1 = create1(); // ok
    auto data2 = create2(); // deleted data_
    auto data3 = create3(); // ok
    auto data4 = create4(); // ok
}

【讨论】:

  • 您能解释一下为什么 create3 中的默认值有效但在构造函数中无效吗?
  • @Fan 我觉得不行。我敢肯定一步之后就会死去。
  • @user4581301 最让我困惑的是 create3 总是有效的。大概和这个有关:stackoverflow.com/questions/2784262/…,而create3中的{}是本地存储在main()中
  • @Fan 无法解释评论中出了什么问题,所以我发布了一个半答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
相关资源
最近更新 更多